diff --git a/.gitignore b/.gitignore index 5f72de53c..f05c5f168 100644 --- a/.gitignore +++ b/.gitignore @@ -156,6 +156,11 @@ cifar-10-batches-py tests/.data tests/data +# Sparrowhawk grammar build cache (regenerable; redundant with the exported +# classify/tokenize_and_classify.far and verbalize/verbalize.far that we do commit). +tools/text_processing_deployment/**/_*_deterministic.far +tools/text_processing_deployment/**/*_itn_*cased.far + # outputs folder examples/*/outputs examples/*/NeMo_experiments diff --git a/nemo_text_processing/inverse_text_normalization/el/data/currency.tsv b/nemo_text_processing/inverse_text_normalization/el/data/currency.tsv new file mode 100644 index 000000000..3da3fc28c --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/currency.tsv @@ -0,0 +1,5 @@ +€ ευρώ +$ δολάριο +$ δολάρια +£ λίρα +£ λίρες diff --git a/nemo_text_processing/inverse_text_normalization/el/data/electronic/domain.tsv b/nemo_text_processing/inverse_text_normalization/el/data/electronic/domain.tsv new file mode 100644 index 000000000..80165e20c --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/electronic/domain.tsv @@ -0,0 +1,8 @@ +com +gr +eu +uk +org +net +gov +edu diff --git a/nemo_text_processing/inverse_text_normalization/el/data/electronic/server_name.tsv b/nemo_text_processing/inverse_text_normalization/el/data/electronic/server_name.tsv new file mode 100644 index 000000000..06ec8f688 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/electronic/server_name.tsv @@ -0,0 +1,4 @@ +gmail +yahoo +hotmail +outlook diff --git a/nemo_text_processing/inverse_text_normalization/el/data/electronic/url_symbols.tsv b/nemo_text_processing/inverse_text_normalization/el/data/electronic/url_symbols.tsv new file mode 100644 index 000000000..47849f7c8 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/electronic/url_symbols.tsv @@ -0,0 +1,4 @@ +. dot +- dash +_ underscore +/ slash diff --git a/nemo_text_processing/inverse_text_normalization/el/data/measurements.tsv b/nemo_text_processing/inverse_text_normalization/el/data/measurements.tsv new file mode 100644 index 000000000..732002968 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/measurements.tsv @@ -0,0 +1,12 @@ +μ μέτρο 1.0 +μ μέτρα 1.0 +m μέτρο +m μέτρα +km χιλιόμετρο +km χιλιόμετρα +kg κιλό +kg κιλά +g γραμμάριο +g γραμμάρια +l λίτρο +l λίτρα diff --git a/nemo_text_processing/inverse_text_normalization/el/data/months.tsv b/nemo_text_processing/inverse_text_normalization/el/data/months.tsv new file mode 100644 index 000000000..e430416dc --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/months.tsv @@ -0,0 +1,36 @@ +Ιανουαρίου 01 +Φεβρουαρίου 02 +Μαρτίου 03 +Απριλίου 04 +Μαΐου 05 +Ιουνίου 06 +Ιουλίου 07 +Αυγούστου 08 +Σεπτεμβρίου 09 +Οκτωβρίου 10 +Νοεμβρίου 11 +Δεκεμβρίου 12 +Ιανουάριος 01 +Φεβρουάριος 02 +Μάρτιος 03 +Απρίλιος 04 +Μάιος 05 +Ιούνιος 06 +Ιούλιος 07 +Αύγουστος 08 +Σεπτέμβριος 09 +Οκτώβριος 10 +Νοέμβριος 11 +Δεκέμβριος 12 +Ιαν 01 +Φεβ 02 +Μαρ 03 +Απρ 04 +Μαϊ 05 +Ιουν 06 +Ιουλ 07 +Αυγ 08 +Σεπ 09 +Οκτ 10 +Νοε 11 +Δεκ 12 diff --git a/nemo_text_processing/inverse_text_normalization/el/data/numbers/digit.tsv b/nemo_text_processing/inverse_text_normalization/el/data/numbers/digit.tsv new file mode 100644 index 000000000..75e6a4ba7 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/numbers/digit.tsv @@ -0,0 +1,18 @@ +ένα 1 +μία 1 +μια 1 +ένας 1 +δύο 2 +δυο 2 +τρία 3 +τρεις 3 +τέσσερα 4 +τέσσερις 4 +πέντε 5 +έξι 6 +εφτά 7 +επτά 7 +οχτώ 8 +οκτώ 8 +εννιά 9 +εννέα 9 diff --git a/nemo_text_processing/inverse_text_normalization/el/data/numbers/hundred.tsv b/nemo_text_processing/inverse_text_normalization/el/data/numbers/hundred.tsv new file mode 100644 index 000000000..08380e02a --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/numbers/hundred.tsv @@ -0,0 +1,37 @@ +εκατό 1 +εκατόν 1 +εκατο 1 +εκατον 1 +διακόσια 2 +διακόσιες 2 +διακόσιοι 2 +τριακόσια 3 +τριακόσιες 3 +τριακόσιοι 3 +τετρακόσια 4 +τετρακόσιες 4 +τετρακόσιοι 4 +πεντακόσια 5 +πεντακόσιες 5 +πεντακόσιοι 5 +εξακόσια 6 +εξακόσιες 6 +εξακόσιοι 6 +εφτακόσια 7 +εφτακόσιες 7 +εφτακόσιοι 7 +επτακόσια 7 +επτακόσιες 7 +επτακόσιοι 7 +οχτακόσια 8 +οχτακόσιες 8 +οχτακόσιοι 8 +οκτακόσια 8 +οκτακόσιες 8 +οκτακόσιοι 8 +εννιακόσια 9 +εννιακόσιες 9 +εννιακόσιοι 9 +εννεακόσια 9 +εννεακόσιες 9 +εννεακόσιοι 9 diff --git a/nemo_text_processing/inverse_text_normalization/el/data/numbers/teen.tsv b/nemo_text_processing/inverse_text_normalization/el/data/numbers/teen.tsv new file mode 100644 index 000000000..1141ccd0e --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/numbers/teen.tsv @@ -0,0 +1,22 @@ +δέκα 10 +δεκα 10 +έντεκα 11 +ένδεκα 11 +εντεκα 11 +ενδεκα 11 +δώδεκα 12 +δωδεκα 12 +δεκατρία 13 +δεκατρείς 13 +δεκατέσσερα 14 +δεκατέσσερις 14 +δεκατέσσερεις 14 +δεκαπέντε 15 +δεκαέξι 16 +δεκάξι 16 +δεκαεφτά 17 +δεκαεπτά 17 +δεκαοχτώ 18 +δεκαοκτώ 18 +δεκαεννέα 19 +δεκαεννιά 19 diff --git a/nemo_text_processing/inverse_text_normalization/el/data/numbers/thousands.tsv b/nemo_text_processing/inverse_text_normalization/el/data/numbers/thousands.tsv new file mode 100644 index 000000000..b4e7009c6 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/numbers/thousands.tsv @@ -0,0 +1,6 @@ +χίλια +χιλιάδες +εκατομμύριο +εκατομμύρια +δισεκατομμύριο +δισεκατομμύρια diff --git a/nemo_text_processing/inverse_text_normalization/el/data/numbers/ties.tsv b/nemo_text_processing/inverse_text_normalization/el/data/numbers/ties.tsv new file mode 100644 index 000000000..b54c8a722 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/numbers/ties.tsv @@ -0,0 +1,16 @@ +είκοσι 2 +εικοσι 2 +τριάντα 3 +τριαντα 3 +σαράντα 4 +σαραντα 4 +πενήντα 5 +πενηντα 5 +εξήντα 6 +εξηντα 6 +εβδομήντα 7 +εβδομηντα 7 +ογδόντα 8 +ογδοντα 8 +ενενήντα 9 +ενενηντα 9 diff --git a/nemo_text_processing/inverse_text_normalization/el/data/numbers/zero.tsv b/nemo_text_processing/inverse_text_normalization/el/data/numbers/zero.tsv new file mode 100644 index 000000000..882ccc5ec --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/numbers/zero.tsv @@ -0,0 +1 @@ +μηδέν 0 diff --git a/nemo_text_processing/inverse_text_normalization/el/data/ordinals/digit.tsv b/nemo_text_processing/inverse_text_normalization/el/data/ordinals/digit.tsv new file mode 100644 index 000000000..a56394d2c --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/ordinals/digit.tsv @@ -0,0 +1,72 @@ +πρώτος 1 +πρώτη 1 +πρώτο 1 +πρώτα 1 +πρωτος 1 +πρωτη 1 +πρωτο 1 +πρωτα 1 +δεύτερος 2 +δεύτερη 2 +δεύτερο 2 +δεύτερα 2 +δευτερος 2 +δευτερη 2 +δευτερο 2 +δευτερα 2 +τρίτος 3 +τρίτη 3 +τρίτο 3 +τρίτα 3 +τριτος 3 +τριτη 3 +τριτο 3 +τριτα 3 +τέταρτος 4 +τέταρτη 4 +τέταρτο 4 +τέταρτα 4 +τεταρτος 4 +τεταρτη 4 +τεταρτο 4 +τεταρτα 4 +πέμπτος 5 +πέμπτη 5 +πέμπτο 5 +πέμπτα 5 +πεμπτος 5 +πεμπτη 5 +πεμπτο 5 +πεμπτα 5 +έκτος 6 +έκτη 6 +έκτο 6 +έκτα 6 +εκτος 6 +εκτη 6 +εκτο 6 +εκτα 6 +έβδομος 7 +έβδομη 7 +έβδομο 7 +έβδομα 7 +εβδομος 7 +εβδομη 7 +εβδομο 7 +εβδομα 7 +όγδοος 8 +όγδοη 8 +όγδοο 8 +όγδοα 8 +ογδοος 8 +ογδοη 8 +ογδοο 8 +ογδοα 8 +ένατος 9 +ένατη 9 +ένατο 9 +ένατα 9 +ενατος 9 +ενατη 9 +ενατο 9 +ενατα 9 diff --git a/nemo_text_processing/inverse_text_normalization/el/data/ordinals/teen.tsv b/nemo_text_processing/inverse_text_normalization/el/data/ordinals/teen.tsv new file mode 100644 index 000000000..1c147968d --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/ordinals/teen.tsv @@ -0,0 +1,24 @@ +δέκατος 10 +δέκατη 10 +δέκατο 10 +δέκατα 10 +δεκατος 10 +δεκατη 10 +δεκατο 10 +δεκατα 10 +ενδέκατος 11 +ενδέκατη 11 +ενδέκατο 11 +ενδέκατα 11 +ενδεκατος 11 +ενδεκατη 11 +ενδεκατο 11 +ενδεκατα 11 +δωδέκατος 12 +δωδέκατη 12 +δωδέκατο 12 +δωδέκατα 12 +δωδεκατος 12 +δωδεκατη 12 +δωδεκατο 12 +δωδεκατα 12 diff --git a/nemo_text_processing/inverse_text_normalization/el/data/ordinals/tens.tsv b/nemo_text_processing/inverse_text_normalization/el/data/ordinals/tens.tsv new file mode 100644 index 000000000..bee412bd1 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/ordinals/tens.tsv @@ -0,0 +1,48 @@ +εικοστός 2 +εικοστή 2 +εικοστό 2 +εικοστά 2 +εικοστου 2 +εικοστης 2 +τριακοστός 3 +τριακοστή 3 +τριακοστό 3 +τριακοστά 3 +τριακοστου 3 +τριακοστης 3 +τεσσαρακοστός 4 +τεσσαρακοστή 4 +τεσσαρακοστό 4 +τεσσαρακοστά 4 +τεσσαρακοστου 4 +τεσσαρακοστης 4 +πεντηκοστός 5 +πεντηκοστή 5 +πεντηκοστό 5 +πεντηκοστά 5 +πεντηκοστου 5 +πεντηκοστης 5 +εξηκοστός 6 +εξηκοστή 6 +εξηκοστό 6 +εξηκοστά 6 +εξηκοστου 6 +εξηκοστης 6 +εβδομηκοστός 7 +εβδομηκοστή 7 +εβδομηκοστό 7 +εβδομηκοστά 7 +εβδομηκοστου 7 +εβδομηκοστης 7 +ογδοηκοστός 8 +ογδοηκοστή 8 +ογδοηκοστό 8 +ογδοηκοστά 8 +ογδοηκοστου 8 +ογδοηκοστης 8 +ενενηκοστός 9 +ενενηκοστή 9 +ενενηκοστό 9 +ενενηκοστά 9 +ενενηκοστου 9 +ενενηκοστης 9 diff --git a/nemo_text_processing/inverse_text_normalization/el/data/whitelist.tsv b/nemo_text_processing/inverse_text_normalization/el/data/whitelist.tsv new file mode 100644 index 000000000..ef5e356d2 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/data/whitelist.tsv @@ -0,0 +1,3 @@ +κ.λπ. και τα λοιπά +κ.ο.κ. και ούτω καθεξής +κ.ά. και άλλα diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/cardinal.py b/nemo_text_processing/inverse_text_normalization/el/taggers/cardinal.py new file mode 100644 index 000000000..e89376721 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/cardinal.py @@ -0,0 +1,106 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_CASED, + INPUT_LOWER_CASED, + NEMO_DIGIT, + NEMO_SIGMA, + NEMO_SPACE, + GraphFst, + capitalized_input_graph, + delete_space, +) + + +class CardinalFst(GraphFst): + def __init__(self, input_case: str = INPUT_LOWER_CASED): + super().__init__(name="cardinal", kind="classify") + + graph_zero = pynini.string_file(get_abs_path("data/numbers/zero.tsv")) + graph_digit = pynini.string_file(get_abs_path("data/numbers/digit.tsv")) + graph_ties = pynini.string_file(get_abs_path("data/numbers/ties.tsv")) + graph_teen = pynini.string_file(get_abs_path("data/numbers/teen.tsv")) + + self.graph_two_digit = graph_teen | ( + (graph_ties | pynutil.insert("0")) + delete_space + (graph_digit | pynutil.insert("0")) + ) + + graph_hundred = pynini.string_file(get_abs_path("data/numbers/hundred.tsv")) + graph_hundred_digit = pynini.union(graph_hundred, pynutil.insert("0")) + graph_hundred_digit += delete_space + graph_hundred_digit += self.graph_two_digit + + def no_all_zeros(fst): + return fst @ (pynini.closure(NEMO_DIGIT) + (NEMO_DIGIT - "0") + pynini.closure(NEMO_DIGIT)) + + graph_hundred_digit_nonzero = no_all_zeros(graph_hundred_digit) + + graph_thousands = pynini.union( + pynini.cross("χίλια", "001"), + pynini.union( + graph_hundred_digit_nonzero + delete_space + pynutil.delete("χιλιάδες"), + pynutil.insert("000", weight=0.1), + ), + ) + + graph_million = pynini.union( + graph_hundred_digit_nonzero + delete_space + pynutil.delete("εκατομμύριο"), + graph_hundred_digit_nonzero + delete_space + pynutil.delete("εκατομμύρια"), + pynutil.insert("000", weight=0.1), + ) + + graph_billion = pynini.union( + graph_hundred_digit_nonzero + delete_space + pynutil.delete("δισεκατομμύριο"), + graph_hundred_digit_nonzero + delete_space + pynutil.delete("δισεκατομμύρια"), + pynutil.insert("000", weight=0.1), + ) + + graph_int = graph_billion + delete_space + graph_million + delete_space + graph_thousands + graph = (graph_int + delete_space + graph_hundred_digit) | graph_zero + graph = graph @ pynini.union( + pynutil.delete(pynini.closure("0")) + pynini.difference(NEMO_DIGIT, "0") + pynini.closure(NEMO_DIGIT), + "0", + ) + + self.graph_no_exception = graph.optimize() + + labels_exception = [ + "μηδέν", + "ένα", + "μία", + "μια", + "ένας", + "δύο", + "δυο", + "τρία", + "τρεις", + "τέσσερα", + "τέσσερις", + "πέντε", + "έξι", + "εφτά", + "επτά", + "οχτώ", + "οκτώ", + "εννιά", + "εννέα", + "δέκα", + "έντεκα", + "ένδεκα", + "δώδεκα", + ] + if input_case == INPUT_CASED: + labels_exception += [x.capitalize() for x in labels_exception] + graph_exception = pynini.union(*labels_exception).optimize() + + self.graph = (pynini.project(graph, "input") - graph_exception.arcsort()) @ graph + self.graph = self.graph.optimize() + + if input_case == INPUT_CASED: + self.graph = capitalized_input_graph(self.graph) + + final_graph = pynutil.insert("integer: \"") + self.graph + pynutil.insert("\"") + final_graph = self.add_tokens(final_graph) + self.fst = final_graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/date.py b/nemo_text_processing/inverse_text_normalization/el/taggers/date.py new file mode 100644 index 000000000..8e9e0ebe5 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/date.py @@ -0,0 +1,37 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_LOWER_CASED, + NEMO_DIGIT, + GraphFst, + delete_extra_space, + delete_space, +) + + +class DateFst(GraphFst): + def __init__(self, cardinal: GraphFst, input_case: str = INPUT_LOWER_CASED): + super().__init__(name="date", kind="classify") + + cardinal_graph = cardinal.graph_no_exception + + month_graph = pynini.string_file(get_abs_path("data/months.tsv")) + month_graph = pynutil.insert("month: \"") + month_graph + pynutil.insert("\"") + + day_graph = pynutil.insert("day: \"") + cardinal_graph + pynutil.insert("\"") + + year_graph = pynutil.insert("year: \"") + cardinal_graph + pynutil.insert("\"") + + graph_dmy = ( + day_graph + delete_extra_space + month_graph + pynini.closure(delete_extra_space + year_graph, 0, 1) + ) + graph_mdy = ( + month_graph + delete_extra_space + day_graph + pynini.closure(delete_extra_space + year_graph, 0, 1) + ) + + final_graph = graph_dmy | graph_mdy + final_graph += pynutil.insert(" preserve_order: true") + final_graph = self.add_tokens(final_graph) + self.fst = final_graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/decimal.py b/nemo_text_processing/inverse_text_normalization/el/taggers/decimal.py new file mode 100644 index 000000000..bd9dbb0da --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/decimal.py @@ -0,0 +1,38 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_LOWER_CASED, + GraphFst, + delete_extra_space, + delete_space, +) + + +class DecimalFst(GraphFst): + def __init__(self, cardinal: GraphFst, input_case: str = INPUT_LOWER_CASED): + super().__init__(name="decimal", kind="classify") + + cardinal_graph = cardinal.graph_no_exception + + graph_digit = pynini.string_file(get_abs_path("data/numbers/digit.tsv")) + graph_digit |= pynini.string_file(get_abs_path("data/numbers/zero.tsv")) + graph_digit_seq = pynini.closure(graph_digit + delete_space) + graph_digit + self.graph = graph_digit + + graph_fractional = ( + pynutil.insert("fractional_part: \"") + (cardinal_graph | graph_digit_seq) + pynutil.insert("\"") + ) + graph_integer = pynutil.insert("integer_part: \"") + cardinal_graph + pynutil.insert("\"") + + point = pynutil.delete("κόμμα") | pynutil.delete("κομμα") + + final_graph_wo_sign = ( + pynini.closure(graph_integer + delete_extra_space, 0, 1) + point + delete_extra_space + graph_fractional + ) + self.final_graph_wo_negative = final_graph_wo_sign + + graph = graph_integer + delete_extra_space + point + delete_extra_space + graph_fractional + graph = self.add_tokens(graph) + self.fst = graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/electronic.py b/nemo_text_processing/inverse_text_normalization/el/taggers/electronic.py new file mode 100644 index 000000000..f219988d9 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/electronic.py @@ -0,0 +1,44 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_LOWER_CASED, + NEMO_ALPHA, + NEMO_DIGIT, + GraphFst, + delete_space, +) + + +class ElectronicFst(GraphFst): + def __init__(self, input_case: str = INPUT_LOWER_CASED): + super().__init__(name="electronic", kind="classify") + + digit = pynini.string_file(get_abs_path("data/numbers/digit.tsv")) | pynini.string_file( + get_abs_path("data/numbers/zero.tsv") + ) + + single_alphanum = NEMO_ALPHA | digit + sequence = single_alphanum + pynini.closure(delete_space + single_alphanum) + + username = pynutil.insert("username: \"") + sequence + pynutil.insert("\"") + + server = sequence + + domain = pynini.string_file(get_abs_path("data/electronic/domain.tsv")) + + domain_graph = ( + pynutil.insert("domain: \"") + + server + + delete_space + + pynini.cross("dot", ".") + + delete_space + + domain + + pynutil.insert("\"") + ) + + graph = username + delete_space + pynutil.delete("at") + delete_space + domain_graph + + final_graph = self.add_tokens(graph) + self.fst = final_graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/fraction.py b/nemo_text_processing/inverse_text_normalization/el/taggers/fraction.py new file mode 100644 index 000000000..039aa82c9 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/fraction.py @@ -0,0 +1,19 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import INPUT_LOWER_CASED, GraphFst, delete_extra_space + + +class FractionFst(GraphFst): + def __init__(self, cardinal: GraphFst, ordinal: GraphFst, input_case: str = INPUT_LOWER_CASED): + super().__init__(name="fraction", kind="classify") + + cardinal_graph = cardinal.graph_no_exception + ordinal_graph = ordinal.graph + + graph_numerator = pynutil.insert("numerator: \"") + cardinal_graph + pynutil.insert("\"") + graph_denominator = pynutil.insert("denominator: \"") + ordinal_graph + pynutil.insert("\"") + + graph = graph_numerator + delete_extra_space + graph_denominator + graph = self.add_tokens(graph) + self.fst = graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/measure.py b/nemo_text_processing/inverse_text_normalization/el/taggers/measure.py new file mode 100644 index 000000000..41db96302 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/measure.py @@ -0,0 +1,45 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_LOWER_CASED, + GraphFst, + convert_space, + delete_extra_space, +) + + +class MeasureFst(GraphFst): + def __init__(self, cardinal: GraphFst, decimal: GraphFst, input_case: str = INPUT_LOWER_CASED): + super().__init__(name="measure", kind="classify") + + cardinal_graph = cardinal.graph_no_exception + + # measurements.tsv maps symbol -> Greek word, listing both singular and plural word forms + # explicitly, so inverting it gives a complete word -> symbol map. (Unlike English there is + # no need for get_singulars: Greek plurals are not formed by appending an "s".) + graph_unit = pynini.invert(pynini.string_file(get_abs_path("data/measurements.tsv"))) + unit = convert_space(graph_unit) + unit_wrapped = pynutil.insert("units: \"") + unit + pynutil.insert("\"") + + subgraph_decimal = ( + pynutil.insert("decimal { ") + + decimal.final_graph_wo_negative + + pynutil.insert(" }") + + delete_extra_space + + unit_wrapped + ) + subgraph_cardinal = ( + pynutil.insert("cardinal { ") + + pynutil.insert("integer: \"") + + cardinal_graph + + pynutil.insert("\"") + + pynutil.insert(" }") + + delete_extra_space + + unit_wrapped + ) + + final_graph = subgraph_decimal | subgraph_cardinal + final_graph = self.add_tokens(final_graph) + self.fst = final_graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/money.py b/nemo_text_processing/inverse_text_normalization/el/taggers/money.py new file mode 100644 index 000000000..de37c5f1b --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/money.py @@ -0,0 +1,41 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_LOWER_CASED, + NEMO_SIGMA, + GraphFst, + convert_space, + delete_extra_space, + delete_space, + get_singulars, +) + + +class MoneyFst(GraphFst): + def __init__(self, cardinal: GraphFst, input_case: str = INPUT_LOWER_CASED): + super().__init__(name="money", kind="classify") + + cardinal_graph = cardinal.graph_no_exception + + unit = pynini.string_file(get_abs_path("data/currency.tsv")) + unit_singular = pynini.invert(unit) + unit_plural = get_singulars(unit_singular) + + graph_unit_singular = pynutil.insert("currency: \"") + convert_space(unit_singular) + pynutil.insert("\"") + graph_unit_plural = ( + pynutil.insert("currency: \"") + convert_space(unit_plural | unit_singular) + pynutil.insert("\"") + ) + + graph_integer = ( + pynutil.insert("integer_part: \"") + + cardinal_graph + + pynutil.insert("\"") + + delete_extra_space + + graph_unit_plural + ) + + final_graph = graph_integer + final_graph = self.add_tokens(final_graph) + self.fst = final_graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/ordinal.py b/nemo_text_processing/inverse_text_normalization/el/taggers/ordinal.py new file mode 100644 index 000000000..a99c10d34 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/ordinal.py @@ -0,0 +1,39 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_CASED, + INPUT_LOWER_CASED, + NEMO_DIGIT, + GraphFst, + capitalized_input_graph, + delete_space, +) + + +class OrdinalFst(GraphFst): + def __init__(self, cardinal: GraphFst, input_case: str = INPUT_LOWER_CASED): + super().__init__(name="ordinal", kind="classify") + + graph_digit = pynini.string_file(get_abs_path("data/ordinals/digit.tsv")) + graph_teens = pynini.string_file(get_abs_path("data/ordinals/teen.tsv")) + graph_tens = pynini.string_file(get_abs_path("data/ordinals/tens.tsv")) + + simple = graph_digit | graph_teens + + tens_digit = pynini.compose(graph_tens, NEMO_DIGIT) + digit_digit = pynini.compose(graph_digit, NEMO_DIGIT) + tens_standalone = graph_tens + pynutil.insert("0") + compound_tens_digit = tens_digit + pynutil.delete(" ") + digit_digit + + compound = tens_standalone | compound_tens_digit + + self.graph = simple | compound + + if input_case == INPUT_CASED: + self.graph = capitalized_input_graph(self.graph) + + final_graph = pynutil.insert("integer: \"") + self.graph + pynutil.insert("\"") + final_graph = self.add_tokens(final_graph) + self.fst = final_graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/punctuation.py b/nemo_text_processing/inverse_text_normalization/el/taggers/punctuation.py new file mode 100644 index 000000000..73fdcffb2 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/punctuation.py @@ -0,0 +1,14 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import GraphFst + + +class PunctuationFst(GraphFst): + def __init__(self): + super().__init__(name="punctuation", kind="classify") + s = "!#$%&\'()*+,-./:;<=>?@^_`{|}~" + greek = "·«»…–—΄" + punct = pynini.union(*s) | pynini.union(*greek) + graph = pynutil.insert("name: \"") + punct + pynutil.insert("\"") + self.fst = graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/telephone.py b/nemo_text_processing/inverse_text_normalization/el/taggers/telephone.py new file mode 100644 index 000000000..d9e616b3c --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/telephone.py @@ -0,0 +1,28 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_LOWER_CASED, + NEMO_DIGIT, + GraphFst, + delete_space, +) + + +class TelephoneFst(GraphFst): + def __init__(self, cardinal: GraphFst, input_case: str = INPUT_LOWER_CASED): + super().__init__(name="telephone", kind="classify") + + digit_to_str = pynini.invert(pynini.string_file(get_abs_path("data/numbers/digit.tsv")).optimize()) + zero = pynini.invert(pynini.string_file(get_abs_path("data/numbers/zero.tsv")).optimize()) + digit_to_str = digit_to_str | zero + str_to_digit = pynini.invert(digit_to_str) + + number_part = pynini.compose( + str_to_digit + pynini.closure(delete_space + str_to_digit), NEMO_DIGIT**10 + pynini.closure(NEMO_DIGIT) + ) + number_part = pynutil.insert("number_part: \"") + number_part + pynutil.insert("\"") + + final_graph = self.add_tokens(number_part) + self.fst = final_graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/time.py b/nemo_text_processing/inverse_text_normalization/el/taggers/time.py new file mode 100644 index 000000000..7e5f0238c --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/time.py @@ -0,0 +1,25 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import INPUT_LOWER_CASED, GraphFst, delete_extra_space + + +class TimeFst(GraphFst): + def __init__(self, cardinal: GraphFst, input_case: str = INPUT_LOWER_CASED): + super().__init__(name="time", kind="classify") + + cardinal_graph = cardinal.graph_no_exception + + hour_restriction = pynini.union(*[str(i) for i in range(24)]) + + graph_hour_limited = pynini.compose(cardinal_graph, hour_restriction) + graph_hour = pynutil.insert("hours: \"") + graph_hour_limited + pynutil.insert("\"") + + minute_restriction = pynini.union(*[str(i) for i in range(60)]) + + graph_minutes_limited = pynini.compose(cardinal_graph, minute_restriction) + graph_minutes = pynutil.insert("minutes: \"") + graph_minutes_limited + pynutil.insert("\"") + + graph = graph_hour + delete_extra_space + graph_minutes + graph = self.add_tokens(graph) + self.fst = graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/tokenize_and_classify.py b/nemo_text_processing/inverse_text_normalization/el/taggers/tokenize_and_classify.py new file mode 100644 index 000000000..168127e15 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/tokenize_and_classify.py @@ -0,0 +1,95 @@ +import os + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.taggers.cardinal import CardinalFst +from nemo_text_processing.inverse_text_normalization.el.taggers.date import DateFst +from nemo_text_processing.inverse_text_normalization.el.taggers.decimal import DecimalFst +from nemo_text_processing.inverse_text_normalization.el.taggers.electronic import ElectronicFst +from nemo_text_processing.inverse_text_normalization.el.taggers.fraction import FractionFst +from nemo_text_processing.inverse_text_normalization.el.taggers.measure import MeasureFst +from nemo_text_processing.inverse_text_normalization.el.taggers.money import MoneyFst +from nemo_text_processing.inverse_text_normalization.el.taggers.ordinal import OrdinalFst +from nemo_text_processing.inverse_text_normalization.el.taggers.punctuation import PunctuationFst +from nemo_text_processing.inverse_text_normalization.el.taggers.telephone import TelephoneFst +from nemo_text_processing.inverse_text_normalization.el.taggers.time import TimeFst +from nemo_text_processing.inverse_text_normalization.el.taggers.whitelist import WhiteListFst +from nemo_text_processing.inverse_text_normalization.el.taggers.word import WordFst +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_LOWER_CASED, + GraphFst, + delete_extra_space, + delete_space, + generator_main, +) +from nemo_text_processing.utils.logging import logger + + +class ClassifyFst(GraphFst): + def __init__( + self, + input_case: str = INPUT_LOWER_CASED, + cache_dir: str = None, + overwrite_cache: bool = False, + whitelist: str = None, + ): + super().__init__(name="tokenize_and_classify", kind="classify") + + far_file = None + if cache_dir is not None and cache_dir != "None": + os.makedirs(cache_dir, exist_ok=True) + far_file = os.path.join(cache_dir, f"el_itn_{input_case}.far") + if not overwrite_cache and far_file and os.path.exists(far_file): + self.fst = pynini.Far(far_file, mode="r")["tokenize_and_classify"] + logger.info(f"ClassifyFst.fst was restored from {far_file}.") + else: + logger.info(f"Creating ClassifyFst grammars.") + cardinal = CardinalFst(input_case=input_case) + cardinal_graph = cardinal.fst + + ordinal = OrdinalFst(cardinal, input_case=input_case) + ordinal_graph = ordinal.fst + + decimal = DecimalFst(cardinal, input_case=input_case) + decimal_graph = decimal.fst + + fraction_graph = FractionFst(cardinal=cardinal, ordinal=ordinal, input_case=input_case).fst + measure_graph = MeasureFst(cardinal=cardinal, decimal=decimal, input_case=input_case).fst + date_graph = DateFst(cardinal=cardinal, input_case=input_case).fst + word_graph = WordFst().fst + time_graph = TimeFst(cardinal=cardinal, input_case=input_case).fst + money_graph = MoneyFst(cardinal=cardinal, input_case=input_case).fst + whitelist_graph = WhiteListFst(input_file=whitelist, input_case=input_case).fst + punct_graph = PunctuationFst().fst + electronic_graph = ElectronicFst(input_case=input_case).fst + telephone_graph = TelephoneFst(cardinal, input_case=input_case).fst + + classify = ( + pynutil.add_weight(whitelist_graph, 1.01) + | pynutil.add_weight(time_graph, 1.1) + | pynutil.add_weight(date_graph, 1.09) + | pynutil.add_weight(decimal_graph, 0.9) + | pynutil.add_weight(measure_graph, 1.1) + | pynutil.add_weight(cardinal_graph, 1.1) + | pynutil.add_weight(ordinal_graph, 1.09) + | pynutil.add_weight(money_graph, 1.1) + | pynutil.add_weight(telephone_graph, 0.9) + | pynutil.add_weight(fraction_graph, 1.1) + | pynutil.add_weight(electronic_graph, 1.1) + | pynutil.add_weight(word_graph, 100) + ) + + punct = pynutil.insert("tokens { ") + pynutil.add_weight(punct_graph, weight=1.1) + pynutil.insert(" }") + token = pynutil.insert("tokens { ") + classify + pynutil.insert(" }") + token_plus_punct = ( + pynini.closure(punct + pynutil.insert(" ")) + token + pynini.closure(pynutil.insert(" ") + punct) + ) + + graph = token_plus_punct + pynini.closure(delete_extra_space + token_plus_punct) + graph = delete_space + graph + delete_space + + self.fst = graph.optimize() + + if far_file: + generator_main(far_file, {"tokenize_and_classify": self.fst}) diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/whitelist.py b/nemo_text_processing/inverse_text_normalization/el/taggers/whitelist.py new file mode 100644 index 000000000..ea15f6b7b --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/whitelist.py @@ -0,0 +1,26 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_CASED, + INPUT_LOWER_CASED, + GraphFst, + capitalized_input_graph, + delete_space, +) + + +class WhiteListFst(GraphFst): + def __init__(self, input_case: str = INPUT_LOWER_CASED, deterministic: bool = True, input_file: str = None): + super().__init__(name="whitelist", kind="classify", deterministic=deterministic) + graph = pynutil.insert("name: \"") + if input_file: + whitelist = pynini.string_file(input_file).invert() + else: + whitelist = pynini.string_file(get_abs_path("data/whitelist.tsv")).invert() + if input_case == INPUT_CASED: + whitelist |= capitalized_input_graph(whitelist) + graph += whitelist + graph += pynutil.insert("\"") + self.fst = graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/taggers/word.py b/nemo_text_processing/inverse_text_normalization/el/taggers/word.py new file mode 100644 index 000000000..6161dd67f --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/taggers/word.py @@ -0,0 +1,11 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_SPACE, GraphFst + + +class WordFst(GraphFst): + def __init__(self): + super().__init__(name="word", kind="classify") + word = pynutil.insert("name: \"") + pynini.closure(NEMO_NOT_SPACE, 1) + pynutil.insert("\"") + self.fst = word.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/utils.py b/nemo_text_processing/inverse_text_normalization/el/utils.py new file mode 100644 index 000000000..2d74c2730 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/utils.py @@ -0,0 +1,19 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + + +def get_abs_path(rel_path): + return os.path.join(os.path.dirname(os.path.abspath(__file__)), rel_path) diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/__init__.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/__init__.py new file mode 100644 index 000000000..bc443be41 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/cardinal.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/cardinal.py new file mode 100644 index 000000000..4c6acb276 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/cardinal.py @@ -0,0 +1,30 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class CardinalFst(GraphFst): + def __init__(self): + super().__init__(name="cardinal", kind="verbalize") + optional_sign = pynini.closure( + pynutil.delete("negative:") + + delete_space + + pynutil.delete("\"") + + NEMO_NOT_QUOTE + + pynutil.delete("\"") + + delete_space, + 0, + 1, + ) + graph = ( + pynutil.delete("integer:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + self.numbers = graph + graph = optional_sign + graph + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/date.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/date.py new file mode 100644 index 000000000..d1c128375 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/date.py @@ -0,0 +1,45 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import ( + NEMO_NOT_QUOTE, + GraphFst, + delete_extra_space, + delete_space, +) + + +class DateFst(GraphFst): + def __init__(self): + super().__init__(name="date", kind="verbalize") + month = ( + pynutil.delete("month:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + day = ( + pynutil.delete("day:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + year = ( + pynutil.delete("year:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + optional_preserve_order = pynini.closure( + pynutil.delete("preserve_order:") + delete_space + pynutil.delete("true") + delete_space + ) + + graph_mdy = month + delete_extra_space + day + pynini.closure(delete_extra_space + year, 0, 1) + graph_dmy = day + delete_extra_space + month + pynini.closure(delete_extra_space + year, 0, 1) + + final_graph = (graph_mdy | graph_dmy) + delete_space + optional_preserve_order + delete_tokens = self.delete_tokens(final_graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/decimal.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/decimal.py new file mode 100644 index 000000000..acd7debb9 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/decimal.py @@ -0,0 +1,40 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class DecimalFst(GraphFst): + def __init__(self): + super().__init__(name="decimal", kind="verbalize") + optionl_sign = pynini.closure(pynini.cross("negative: \"true\"", "-") + delete_space, 0, 1) + integer = ( + pynutil.delete("integer_part:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + optional_integer = pynini.closure(integer + delete_space, 0, 1) + fractional = ( + pynutil.insert(",") + + pynutil.delete("fractional_part:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + optional_fractional = pynini.closure(fractional + delete_space, 0, 1) + quantity = ( + pynutil.delete("quantity:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + optional_quantity = pynini.closure(pynutil.insert(" ") + quantity + delete_space, 0, 1) + graph = optional_integer + optional_fractional + optional_quantity + self.numbers = graph + graph = optionl_sign + graph + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/electronic.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/electronic.py new file mode 100644 index 000000000..d344bdd21 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/electronic.py @@ -0,0 +1,27 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class ElectronicFst(GraphFst): + def __init__(self): + super().__init__(name="electronic", kind="verbalize") + user_name = ( + pynutil.delete("username:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + domain = ( + pynutil.delete("domain:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + + graph = user_name + delete_space + pynutil.insert("@") + domain + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/fraction.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/fraction.py new file mode 100644 index 000000000..de722a022 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/fraction.py @@ -0,0 +1,16 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class FractionFst(GraphFst): + def __init__(self): + super().__init__(name="fraction", kind="verbalize") + + numerator = pynutil.delete("numerator: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + denominator = pynutil.delete("denominator: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + + graph = numerator + delete_space + pynutil.insert("/") + denominator + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/measure.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/measure.py new file mode 100644 index 000000000..4e3a6269d --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/measure.py @@ -0,0 +1,39 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_CHAR, GraphFst, delete_space + + +class MeasureFst(GraphFst): + def __init__(self, decimal: GraphFst, cardinal: GraphFst): + super().__init__(name="measure", kind="verbalize") + optional_sign = pynini.closure(pynini.cross("negative: \"true\"", "-"), 0, 1) + unit = ( + pynutil.delete("units:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_CHAR - " ", 1) + + pynutil.delete("\"") + + delete_space + ) + graph_decimal = ( + pynutil.delete("decimal {") + + delete_space + + optional_sign + + delete_space + + decimal.numbers + + delete_space + + pynutil.delete("}") + ) + graph_cardinal = ( + pynutil.delete("cardinal {") + + delete_space + + optional_sign + + delete_space + + cardinal.numbers + + delete_space + + pynutil.delete("}") + ) + graph = (graph_cardinal | graph_decimal) + delete_space + pynutil.insert(" ") + unit + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/money.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/money.py new file mode 100644 index 000000000..56c0672d8 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/money.py @@ -0,0 +1,19 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_CHAR, GraphFst, delete_space + + +class MoneyFst(GraphFst): + def __init__(self, decimal: GraphFst): + super().__init__(name="money", kind="verbalize") + unit = ( + pynutil.delete("currency:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_CHAR - " ", 1) + + pynutil.delete("\"") + ) + graph = unit + delete_space + decimal.numbers + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/ordinal.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/ordinal.py new file mode 100644 index 000000000..b0e2779fa --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/ordinal.py @@ -0,0 +1,18 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class OrdinalFst(GraphFst): + def __init__(self): + super().__init__(name="ordinal", kind="verbalize") + graph = ( + pynutil.delete("integer:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/telephone.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/telephone.py new file mode 100644 index 000000000..3d680e869 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/telephone.py @@ -0,0 +1,21 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst + + +class TelephoneFst(GraphFst): + def __init__(self): + super().__init__(name="telephone", kind="verbalize") + + number_part = pynutil.delete("number_part: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + optional_country_code = pynini.closure( + pynutil.delete("country_code: \"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + + pynini.accep(" "), + 0, + 1, + ) + delete_tokens = self.delete_tokens(optional_country_code + number_part) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/time.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/time.py new file mode 100644 index 000000000..169b67e3b --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/time.py @@ -0,0 +1,49 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import ( + NEMO_CHAR, + NEMO_DIGIT, + GraphFst, + delete_space, + insert_space, +) + + +class TimeFst(GraphFst): + def __init__(self): + super().__init__(name="time", kind="verbalize") + add_leading_zero_to_double_digit = (NEMO_DIGIT + NEMO_DIGIT) | (pynutil.insert("0") + NEMO_DIGIT) + hour = ( + pynutil.delete("hours:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_DIGIT, 1) + + pynutil.delete("\"") + ) + minute = ( + pynutil.delete("minutes:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_DIGIT, 1) + + pynutil.delete("\"") + ) + suffix = ( + delete_space + + insert_space + + pynutil.delete("suffix:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_CHAR - " ", 1) + + pynutil.delete("\"") + ) + optional_suffix = pynini.closure(suffix, 0, 1) + graph = ( + hour @ add_leading_zero_to_double_digit + + delete_space + + pynutil.insert(":") + + (minute @ add_leading_zero_to_double_digit) + + optional_suffix + ) + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/verbalize.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/verbalize.py new file mode 100644 index 000000000..ea9e053c9 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/verbalize.py @@ -0,0 +1,44 @@ +from nemo_text_processing.inverse_text_normalization.el.verbalizers.cardinal import CardinalFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.date import DateFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.decimal import DecimalFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.electronic import ElectronicFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.fraction import FractionFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.measure import MeasureFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.money import MoneyFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.ordinal import OrdinalFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.telephone import TelephoneFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.time import TimeFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.whitelist import WhiteListFst +from nemo_text_processing.text_normalization.en.graph_utils import GraphFst + + +class VerbalizeFst(GraphFst): + def __init__(self): + super().__init__(name="verbalize", kind="verbalize") + cardinal = CardinalFst() + cardinal_graph = cardinal.fst + ordinal_graph = OrdinalFst().fst + decimal = DecimalFst() + decimal_graph = decimal.fst + measure_graph = MeasureFst(decimal=decimal, cardinal=cardinal).fst + money_graph = MoneyFst(decimal=decimal).fst + time_graph = TimeFst().fst + date_graph = DateFst().fst + whitelist_graph = WhiteListFst().fst + telephone_graph = TelephoneFst().fst + electronic_graph = ElectronicFst().fst + fraction_graph = FractionFst().fst + graph = ( + time_graph + | date_graph + | money_graph + | measure_graph + | ordinal_graph + | decimal_graph + | cardinal_graph + | fraction_graph + | whitelist_graph + | telephone_graph + | electronic_graph + ) + self.fst = graph diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/verbalize_final.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/verbalize_final.py new file mode 100644 index 000000000..c6a5e0304 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/verbalize_final.py @@ -0,0 +1,25 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.inverse_text_normalization.el.verbalizers.verbalize import VerbalizeFst +from nemo_text_processing.inverse_text_normalization.el.verbalizers.word import WordFst +from nemo_text_processing.text_normalization.en.graph_utils import GraphFst, delete_extra_space, delete_space + + +class VerbalizeFinalFst(GraphFst): + def __init__(self): + super().__init__(name="verbalize_final", kind="verbalize") + verbalize = VerbalizeFst().fst + word = WordFst().fst + types = verbalize | word + graph = ( + pynutil.delete("tokens") + + delete_space + + pynutil.delete("{") + + delete_space + + types + + delete_space + + pynutil.delete("}") + ) + graph = delete_space + pynini.closure(graph + delete_extra_space) + graph + delete_space + self.fst = graph diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/whitelist.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/whitelist.py new file mode 100644 index 000000000..3e1d1b295 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/whitelist.py @@ -0,0 +1,18 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_CHAR, NEMO_SIGMA, GraphFst, delete_space + + +class WhiteListFst(GraphFst): + def __init__(self): + super().__init__(name="whitelist", kind="verbalize") + graph = ( + pynutil.delete("name:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_CHAR - " ", 1) + + pynutil.delete("\"") + ) + graph = graph @ pynini.cdrewrite(pynini.cross(u"\u00a0", " "), "", "", NEMO_SIGMA) + self.fst = graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/el/verbalizers/word.py b/nemo_text_processing/inverse_text_normalization/el/verbalizers/word.py new file mode 100644 index 000000000..435d27f55 --- /dev/null +++ b/nemo_text_processing/inverse_text_normalization/el/verbalizers/word.py @@ -0,0 +1,14 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_CHAR, NEMO_SIGMA, GraphFst, delete_space + + +class WordFst(GraphFst): + def __init__(self): + super().__init__(name="word", kind="verbalize") + chars = pynini.closure(NEMO_CHAR - " ", 1) + char = pynutil.delete("name:") + delete_space + pynutil.delete("\"") + chars + pynutil.delete("\"") + graph = char @ pynini.cdrewrite(pynini.cross(u"\u00a0", " "), "", "", NEMO_SIGMA) + + self.fst = graph.optimize() diff --git a/nemo_text_processing/inverse_text_normalization/inverse_normalize.py b/nemo_text_processing/inverse_text_normalization/inverse_normalize.py index 9a6fcc64c..11ee517db 100644 --- a/nemo_text_processing/inverse_text_normalization/inverse_normalize.py +++ b/nemo_text_processing/inverse_text_normalization/inverse_normalize.py @@ -49,7 +49,6 @@ def __init__( overwrite_cache: bool = False, max_number_of_permutations_per_split: int = 729, ): - assert input_case in ["lower_cased", "cased"] if lang == 'en': # English @@ -146,6 +145,11 @@ def __init__( from nemo_text_processing.inverse_text_normalization.ko.verbalizers.verbalize_final import ( VerbalizeFinalFst, ) + elif lang == 'el': # Greek + from nemo_text_processing.inverse_text_normalization.el.taggers.tokenize_and_classify import ClassifyFst + from nemo_text_processing.inverse_text_normalization.el.verbalizers.verbalize_final import ( + VerbalizeFinalFst, + ) else: raise NotImplementedError(f"Language {lang} has not been supported yet.") @@ -211,6 +215,7 @@ def parse_args(): 'mr', 'ja', 'ko', + 'el', ], default="en", type=str, diff --git a/nemo_text_processing/text_normalization/el/__init__.py b/nemo_text_processing/text_normalization/el/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/__init__.py b/nemo_text_processing/text_normalization/el/data/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/address/address_word.tsv b/nemo_text_processing/text_normalization/el/data/address/address_word.tsv new file mode 100644 index 000000000..4cb5e4d91 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/address/address_word.tsv @@ -0,0 +1,8 @@ +οδ Οδός +λεωφ Λεωφόρος +δρ Δρόμος +πλ Πλατεία +λεωφόρος Λεωφόρος +δρόμος Δρόμος +πλατεία Πλατεία +πάροδος Πάροδος diff --git a/nemo_text_processing/text_normalization/el/data/address/state.tsv b/nemo_text_processing/text_normalization/el/data/address/state.tsv new file mode 100644 index 000000000..a1f5af15b --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/address/state.tsv @@ -0,0 +1,14 @@ +Αττική Αττικής +Θεσσαλονίκη Θεσσαλονίκης +Κεντρική Μακεδονία Κεντρικής Μακεδονίας +Πελοπόννησος Πελοποννήσου +Κρήτη Κρήτης +Θεσσαλία Θεσσαλίας +Ήπειρος Ηπείρου +Στερεά Ελλάδα Στερεάς Ελλάδας +Ανατολική Μακεδονία και Θράκη Ανατολικής Μακεδονίας και Θράκης +Βόρειο Αιγαίο Βορείου Αιγαίου +Νότιο Αιγαίο Νοτίου Αιγαίου +Δυτική Ελλάδα Δυτικής Ελλάδας +Δυτική Μακεδονία Δυτικής Μακεδονίας +Ιόνια Νησιά Ιονίων Νησιών diff --git a/nemo_text_processing/text_normalization/el/data/dates/__init__.py b/nemo_text_processing/text_normalization/el/data/dates/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/dates/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/dates/months.tsv b/nemo_text_processing/text_normalization/el/data/dates/months.tsv new file mode 100644 index 000000000..0601fe639 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/dates/months.tsv @@ -0,0 +1,12 @@ +01 Ιανουαρίου +02 Φεβρουαρίου +03 Μαρτίου +04 Απριλίου +05 Μαΐου +06 Ιουνίου +07 Ιουλίου +08 Αυγούστου +09 Σεπτεμβρίου +10 Οκτωβρίου +11 Νοεμβρίου +12 Δεκεμβρίου diff --git a/nemo_text_processing/text_normalization/el/data/electronic/__init__.py b/nemo_text_processing/text_normalization/el/data/electronic/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/electronic/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/measure/__init__.py b/nemo_text_processing/text_normalization/el/data/measure/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/measure/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/measure/math_operation.tsv b/nemo_text_processing/text_normalization/el/data/measure/math_operation.tsv new file mode 100644 index 000000000..b20b60730 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/measure/math_operation.tsv @@ -0,0 +1,8 @@ ++ συν +- πλην +/ δια +÷ δια +: δια +× επί +* επί +· επί diff --git a/nemo_text_processing/text_normalization/el/data/measure/units.tsv b/nemo_text_processing/text_normalization/el/data/measure/units.tsv new file mode 100644 index 000000000..daa301a83 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/measure/units.tsv @@ -0,0 +1,30 @@ +χλμ χιλιόμετρα +km χιλιόμετρα +μ μέτρα +m μέτρα +εκ εκατοστά +cm εκατοστά +χγρ κιλά +κιλά κιλά +kg κιλά +γρ γραμμάρια +g γραμμάρια +λ λίτρα +l λίτρα +ml χιλιοστόλιτρα +kg/m2 κιλά ανά τετραγωνικό μέτρο +km/h χιλιόμετρα ανά ώρα +°C βαθμοί Κελσίου +°F βαθμοί Φαρενάιτ +W βατ +kW κιλοβάτ +kWh κιλοβατώρες +V βολτ +A αμπέρ +Hz χερτζ +MHz μεγαχέρτζ +GHz γιγαχέρτζ +mm χιλιοστά +m2 τετραγωνικά μέτρα +m3 κυβικά μέτρα +cm2 τετραγωνικά εκατοστά diff --git a/nemo_text_processing/text_normalization/el/data/money/__init__.py b/nemo_text_processing/text_normalization/el/data/money/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/money/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/numbers/__init__.py b/nemo_text_processing/text_normalization/el/data/numbers/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/numbers/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/numbers/digit.tsv b/nemo_text_processing/text_normalization/el/data/numbers/digit.tsv new file mode 100644 index 000000000..d34cd56b3 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/numbers/digit.tsv @@ -0,0 +1,10 @@ +0 μηδέν +1 ένα +2 δύο +3 τρία +4 τέσσερα +5 πέντε +6 έξι +7 εφτά +8 οχτώ +9 εννιά diff --git a/nemo_text_processing/text_normalization/el/data/numbers/hundreds.tsv b/nemo_text_processing/text_normalization/el/data/numbers/hundreds.tsv new file mode 100644 index 000000000..4c54e5543 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/numbers/hundreds.tsv @@ -0,0 +1,8 @@ +2 διακόσια +3 τριακόσια +4 τετρακόσια +5 πεντακόσια +6 εξακόσια +7 εφτακόσια +8 οχτακόσια +9 εννιακόσια diff --git a/nemo_text_processing/text_normalization/el/data/numbers/teen.tsv b/nemo_text_processing/text_normalization/el/data/numbers/teen.tsv new file mode 100644 index 000000000..35e1644cd --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/numbers/teen.tsv @@ -0,0 +1,10 @@ +10 δέκα +11 έντεκα +12 δώδεκα +13 δεκατρία +14 δεκατέσσερα +15 δεκαπέντε +16 δεκαέξι +17 δεκαεφτά +18 δεκαοχτώ +19 δεκαεννιά diff --git a/nemo_text_processing/text_normalization/el/data/numbers/ties.tsv b/nemo_text_processing/text_normalization/el/data/numbers/ties.tsv new file mode 100644 index 000000000..fe9c809ce --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/numbers/ties.tsv @@ -0,0 +1,8 @@ +2 είκοσι +3 τριάντα +4 σαράντα +5 πενήντα +6 εξήντα +7 εβδομήντα +8 ογδόντα +9 ενενήντα diff --git a/nemo_text_processing/text_normalization/el/data/ordinal/__init__.py b/nemo_text_processing/text_normalization/el/data/ordinal/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/ordinal/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/ordinal/digit.tsv b/nemo_text_processing/text_normalization/el/data/ordinal/digit.tsv new file mode 100644 index 000000000..c61d040fa --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/ordinal/digit.tsv @@ -0,0 +1,9 @@ +1 πρώτος +2 δεύτερος +3 τρίτος +4 τέταρτος +5 πέμπτος +6 έκτος +7 έβδομος +8 όγδοος +9 ένατος diff --git a/nemo_text_processing/text_normalization/el/data/ordinal/hundreds.tsv b/nemo_text_processing/text_normalization/el/data/ordinal/hundreds.tsv new file mode 100644 index 000000000..1a07e8aec --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/ordinal/hundreds.tsv @@ -0,0 +1,9 @@ +1 εκατοστός +2 διακοσιοστός +3 τριακοσιοστός +4 τετρακοσιοστός +5 πεντακοσιοστός +6 εξακοσιοστός +7 εφτακοσιοστός +8 οχτακοσιοστός +9 εννιακοσιοστός diff --git a/nemo_text_processing/text_normalization/el/data/ordinal/teen.tsv b/nemo_text_processing/text_normalization/el/data/ordinal/teen.tsv new file mode 100644 index 000000000..9efba47a7 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/ordinal/teen.tsv @@ -0,0 +1,10 @@ +10 δέκατος +11 ενδέκατος +12 δωδέκατος +13 δέκατος τρίτος +14 δέκατος τέταρτος +15 δέκατος πέμπτος +16 δέκατος έκτος +17 δέκατος έβδομος +18 δέκατος όγδοος +19 δέκατος ένατος diff --git a/nemo_text_processing/text_normalization/el/data/ordinal/ties.tsv b/nemo_text_processing/text_normalization/el/data/ordinal/ties.tsv new file mode 100644 index 000000000..ca08124c5 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/ordinal/ties.tsv @@ -0,0 +1,8 @@ +2 εικοστός +3 τριακοστός +4 τεσσαρακοστός +5 πεντηκοστός +6 εξηκοστός +7 εβδομηκοστός +8 ογδοηκοστός +9 ενενηκοστός diff --git a/nemo_text_processing/text_normalization/el/data/roman/__init__.py b/nemo_text_processing/text_normalization/el/data/roman/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/roman/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/roman/female.tsv b/nemo_text_processing/text_normalization/el/data/roman/female.tsv new file mode 100644 index 000000000..217234701 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/roman/female.tsv @@ -0,0 +1,274 @@ +Αγγελική +Αγλαΐα +Αθηνά +Αικατερίνη +Αλεξάνδρα +Αμαλία +Αναστασία +Ανδρομάχη +Ανθούλα +Αντιγόνη +Απολλωνία +Αργυρώ +Αριάδνη +Αρτεμισία +Ασημίνα +Αφροδίτη +Βασιλική +Βίκυ +Γαλάτεια +Γεωργία +Γλυκερία +Δάφνη +Δέσποινα +Δήμητρα +Διαμάντω +Δωροθέα +Ειρήνη +Έλενα +Ελένη +Ελισάβετ +Ελπίδα +Εμμανουέλα +Ερασμία +Ευγενία +Ευδοκία +Ευθαλία +Ευτέρπη +Ζαμπέτα +Ζωή +Ηλέκτρα +Θάλεια +Θεοδώρα +Θεοφανώ +Ιουλία +Ισμήνη +Ιωάννα +Καλή +Καλλιόπη +Κασσάνδρα +Κατερίνα +Κλεοπάτρα +Κορνηλία +Κυριακή +Κωνσταντίνα +Λαμπρινή +Λήδα +Λουκία +Λυδία +Μαγδαληνή +Μαργαρίτα +Μαρία +Μαρίνα +Μελισσάνθη +Μελπομένη +Μερόπη +Μιχαέλα +Μυρσίνη +Μυρτώ +Ναταλία +Νίκη +Νικολέτα +Ξανθή +Ξένια +Ολυμπία +Ουρανία +Παναγιώτα +Πανδώρα +Παρασκευή +Πελαγία +Πηνελόπη +Πολυξένη +Πωλίνα +Ρεβέκκα +Ροδούλα +Σαπφώ +Σμαράγδα +Σοφία +Σταυρούλα +Στέλλα +Στεργιανή +Στυλιανή +Σύλβια +Τατιάνα +Τερέζα +Τριανταφυλλιά +Φαίδρα +Φανή +Φιλομήλα +Φλωρεντία +Φρόσω +Χαρά +Χαρίκλεια +Χριστίνα +Χρυσάνθη +Χρυσούλα +Ψυχή +Ωραιάνθη +Ada +Adelaide +Agatha +Agnes +Alexandra +Alice +Alison +Amanda +Amelia +Amy +Andrea +Angela +Ann +Anna +Anne +Annabelle +Antonia +Ariana +Ashley +Audrey +Barbara +Beatrice +Brittany +Brooke +Camilla +Catherine +Charlotte +Chloe +Christina +Christine +Claire +Courtney +Cynthia +Daisy +Danielle +Daphne +Dawn +Deborah +Diana +Donna +Doris +Dorothy +Edith +Elizabeth +Ella +Emily +Emma +Esther +Eva +Evelyn +Faith +Fiona +Florence +Frances +Francesca +Gabrielle +Georgia +Geraldine +Gloria +Grace +Hannah +Harriet +Hazel +Heather +Helen +Henrietta +Hilary +Holly +Irene +Isabel +Isabella +Jacqueline +Jade +Jane +Janet +Jean +Jennifer +Jessica +Joan +Joanna +Jocelyn +Josephine +Joyce +Judith +Julia +Julie +June +Karen +Katherine +Kathleen +Kimberly +Laura +Lauren +Lillian +Lily +Linda +Lisa +Louise +Lucy +Lydia +Lynn +Madeline +Margaret +Maria +Martha +Mary +Megan +Melanie +Melissa +Michelle +Molly +Monica +Nancy +Natalie +Nicole +Nora +Olivia +Pamela +Patricia +Paula +Pearl +Peggy +Penelope +Philippa +Phoebe +Phyllis +Polly +Priscilla +Rachel +Rebecca +Rita +Roberta +Rose +Rosemary +Ruth +Samantha +Sandra +Sara +Sarah +Sharon +Sheila +Shirley +Stephanie +Susan +Susanna +Suzanne +Sylvia +Teresa +Tina +Tracy +Valerie +Vanessa +Vera +Veronica +Victoria +Violet +Virginia +Vivian +Wendy +Winifred +Ζηνοβία +Άννα +Έλλη +Έφη +Ήβη +Ίρις +Όλγα diff --git a/nemo_text_processing/text_normalization/el/data/roman/key_word.tsv b/nemo_text_processing/text_normalization/el/data/roman/key_word.tsv new file mode 100644 index 000000000..d134a8faf --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/roman/key_word.tsv @@ -0,0 +1,24 @@ +Κεφάλαιο chapter +κεφάλαιο chapter +Κεφ chapter +κεφ chapter +Μέρος part +μέρος part +Τεύχος volume +τεύχος volume +Τόμος volume +τόμος volume +Άρθρο article +άρθρο article +Πίνακας table +πίνακας table +Ενότητα section +ενότητα section +Παράρτημα appendix +παράρτημα appendix +Σελίδα page +σελίδα page +Εικόνα figure +εικόνα figure +Παράγραφος paragraph +παράγραφος paragraph diff --git a/nemo_text_processing/text_normalization/el/data/roman/male.tsv b/nemo_text_processing/text_normalization/el/data/roman/male.tsv new file mode 100644 index 000000000..2953f964a --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/roman/male.tsv @@ -0,0 +1,317 @@ +Αβραάμ +Αγαμέμνων +Αγάπιος +Αγγελής +Αγησίλαος +Αδαμάντιος +Αθανάσιος +Αιμίλιος +Αλέξανδρος +Αλέξιος +Αλκιβιάδης +Αμβρόσιος +Αναξαγόρας +Ανδρέας +Ανδρόνικος +Ανέστης +Αντώνιος +Απόστολος +Αργύρης +Αριστείδης +Αριστοτέλης +Αρκάδιος +Αρσένιος +Αρχιμήδης +Ασημάκης +Βασίλειος +Βενέδικτος +Βησσαρίωνας +Βλάσιος +Βρασίδας +Γαβριήλ +Γεράσιμος +Γεώργιος +Γιάννης +Γρηγόριος +Δαβίδ +Δαμιανός +Δανιήλ +Δημήτριος +Διογένης +Διονύσιος +Δομήνικος +Δωρόθεος +Ελευθέριος +Ελισαίος +Εμμανουήλ +Επαμεινώνδας +Επίκουρος +Ερμής +Ερμόλαος +Ευάγγελος +Ευγένιος +Ευθύμιος +Ευμένης +Ευριπίδης +Ευστάθιος +Ευστράτιος +Ζαχαρίας +Ζήνωνας +Ηλίας +Ηρακλής +Θεμιστοκλής +Θεόδωρος +Θεόφιλος +Θεοχάρης +Θρασύβουλος +Ιάκωβος +Ιάσονας +Ιγνάτιος +Ιερεμίας +Ιερόθεος +Ιερώνυμος +Ιορδάνης +Ιούλιος +Ιπποκράτης +Ισαάκ +Ισίδωρος +Ιωάννης +Ιωακείμ +Ιωσήφ +Κάρολος +Κλεάνθης +Κλέων +Κομνηνός +Κοσμάς +Κυπριανός +Κύριλλος +Κωνσταντίνος +Λάζαρος +Λάμπρος +Λεωνίδας +Λουκάς +Λουκιανός +Λυκούργος +Μάγνος +Μακάριος +Μαρίνος +Μάρκος +Ματθαίος +Μιλτιάδης +Μιχαήλ +Μωυσής +Νέστωρ +Νικήτας +Νικηφόρος +Νικόδημος +Νικόλαος +Νίκος +Ξενοφώντας +Οδυσσέας +Ορέστης +Παγκράτιος +Παναγής +Παναγιώτης +Παντελής +Παρασκευάς +Παύλος +Πέτρος +Πλάτων +Πλούταρχος +Πολύκαρπος +Πορφύριος +Προκόπιος +Πρωτεσίλαος +Πυθαγόρας +Ραφαήλ +Ρήγας +Σάββας +Σεβαστιανός +Σέργιος +Σίμων +Σολομών +Σπυρίδων +Σταμάτης +Σταύρος +Στέφανος +Στυλιανός +Σωκράτης +Σωτήρης +Τηλέμαχος +Τίμων +Τίτος +Τριαντάφυλλος +Τρύφων +Υάκινθος +Φανούριος +Φίλιππος +Φιλόθεος +Φιλώτας +Φίλων +Φωκάς +Φώτιος +Χαράλαμπος +Χαρίλαος +Χρήστος +Χριστόδουλος +Χριστόφορος +Aaron +Adam +Adrian +Alan +Albert +Alexander +Alfred +Andrew +Anthony +Arthur +Augustus +Benjamin +Bernard +Bertrand +Brian +Bruce +Carl +Charles +Christian +Christopher +Clark +Claude +Conrad +Constantine +Cyril +Daniel +David +Dennis +Donald +Douglas +Duke +Edgar +Edmund +Edward +Edwin +Emil +Eric +Ernest +Eugene +Felix +Ferdinand +Francis +Frank +Frederick +Gabriel +Geoffrey +George +Gerald +Gilbert +Gordon +Gregory +Gustav +Harold +Harrison +Harry +Henry +Herbert +Herman +Hugh +Ian +Isaac +Ivan +Jack +Jacob +James +Jason +Jeffrey +Jeremy +Jerome +Jesse +John +Jonathan +Joseph +Joshua +Julian +Justin +Keith +Kenneth +Kevin +Kyle +Laurence +Lawrence +Leo +Leonard +Leopold +Leslie +Lewis +Liam +Louis +Lucas +Luke +Malcolm +Marcus +Mark +Martin +Matthew +Maurice +Max +Michael +Nathan +Nathaniel +Neil +Nicholas +Nigel +Noah +Norman +Oliver +Oscar +Oswald +Owen +Patrick +Paul +Peter +Philip +Phillip +Quentin +Ralph +Randall +Raymond +Reginald +Richard +Robert +Robin +Roger +Roland +Ronald +Roy +Rudolf +Russell +Ryan +Samuel +Sean +Sebastian +Seth +Sidney +Silas +Simon +Stephen +Steven +Stewart +Stuart +Terence +Theodore +Thomas +Timothy +Tobias +Tyler +Victor +Vincent +Vladimir +Walter +Warren +Wayne +William +Winston +Zachary +Άγγελος +Έκτορας +Έρασμος +Όμηρος diff --git a/nemo_text_processing/text_normalization/el/data/roman/roman_to_spoken.tsv b/nemo_text_processing/text_normalization/el/data/roman/roman_to_spoken.tsv new file mode 100644 index 000000000..43cf0cc5f --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/roman/roman_to_spoken.tsv @@ -0,0 +1,3000 @@ +I ένα +II δύο +III τρία +IV τέσσερα +V πέντε +VI έξι +VII εφτά +VIII οχτώ +IX εννιά +X δέκα +XI έντεκα +XII δώδεκα +XIII δεκατρία +XIV δεκατέσσερα +XV δεκαπέντε +XVI δεκαέξι +XVII δεκαεφτά +XVIII δεκαοχτώ +XIX δεκαεννιά +XX είκοσι +XXI είκοσι ένα +XXII είκοσι δύο +XXIII είκοσι τρία +XXIV είκοσι τέσσερα +XXV είκοσι πέντε +XXVI είκοσι έξι +XXVII είκοσι εφτά +XXVIII είκοσι οχτώ +XXIX είκοσι εννιά +XXX τριάντα +XXXI τριάντα ένα +XXXII τριάντα δύο +XXXIII τριάντα τρία +XXXIV τριάντα τέσσερα +XXXV τριάντα πέντε +XXXVI τριάντα έξι +XXXVII τριάντα εφτά +XXXVIII τριάντα οχτώ +XXXIX τριάντα εννιά +XL σαράντα +XLI σαράντα ένα +XLII σαράντα δύο +XLIII σαράντα τρία +XLIV σαράντα τέσσερα +XLV σαράντα πέντε +XLVI σαράντα έξι +XLVII σαράντα εφτά +XLVIII σαράντα οχτώ +XLIX σαράντα εννιά +L πενήντα +LI πενήντα ένα +LII πενήντα δύο +LIII πενήντα τρία +LIV πενήντα τέσσερα +LV πενήντα πέντε +LVI πενήντα έξι +LVII πενήντα εφτά +LVIII πενήντα οχτώ +LIX πενήντα εννιά +LX εξήντα +LXI εξήντα ένα +LXII εξήντα δύο +LXIII εξήντα τρία +LXIV εξήντα τέσσερα +LXV εξήντα πέντε +LXVI εξήντα έξι +LXVII εξήντα εφτά +LXVIII εξήντα οχτώ +LXIX εξήντα εννιά +LXX εβδομήντα +LXXI εβδομήντα ένα +LXXII εβδομήντα δύο +LXXIII εβδομήντα τρία +LXXIV εβδομήντα τέσσερα +LXXV εβδομήντα πέντε +LXXVI εβδομήντα έξι +LXXVII εβδομήντα εφτά +LXXVIII εβδομήντα οχτώ +LXXIX εβδομήντα εννιά +LXXX ογδόντα +LXXXI ογδόντα ένα +LXXXII ογδόντα δύο +LXXXIII ογδόντα τρία +LXXXIV ογδόντα τέσσερα +LXXXV ογδόντα πέντε +LXXXVI ογδόντα έξι +LXXXVII ογδόντα εφτά +LXXXVIII ογδόντα οχτώ +LXXXIX ογδόντα εννιά +XC ενενήντα +XCI ενενήντα ένα +XCII ενενήντα δύο +XCIII ενενήντα τρία +XCIV ενενήντα τέσσερα +XCV ενενήντα πέντε +XCVI ενενήντα έξι +XCVII ενενήντα εφτά +XCVIII ενενήντα οχτώ +XCIX ενενήντα εννιά +C εκατό +CI εκατόν ένα +CII εκατόν δύο +CIII εκατόν τρία +CIV εκατόν τέσσερα +CV εκατόν πέντε +CVI εκατόν έξι +CVII εκατόν εφτά +CVIII εκατόν οχτώ +CIX εκατόν εννιά +CX εκατόν δέκα +CXI εκατόν έντεκα +CXII εκατόν δώδεκα +CXIII εκατόν δεκατρία +CXIV εκατόν δεκατέσσερα +CXV εκατόν δεκαπέντε +CXVI εκατόν δεκαέξι +CXVII εκατόν δεκαεφτά +CXVIII εκατόν δεκαοχτώ +CXIX εκατόν δεκαεννιά +CXX εκατόν είκοσι +CXXI εκατόν είκοσι ένα +CXXII εκατόν είκοσι δύο +CXXIII εκατόν είκοσι τρία +CXXIV εκατόν είκοσι τέσσερα +CXXV εκατόν είκοσι πέντε +CXXVI εκατόν είκοσι έξι +CXXVII εκατόν είκοσι εφτά +CXXVIII εκατόν είκοσι οχτώ +CXXIX εκατόν είκοσι εννιά +CXXX εκατόν τριάντα +CXXXI εκατόν τριάντα ένα +CXXXII εκατόν τριάντα δύο +CXXXIII εκατόν τριάντα τρία +CXXXIV εκατόν τριάντα τέσσερα +CXXXV εκατόν τριάντα πέντε +CXXXVI εκατόν τριάντα έξι +CXXXVII εκατόν τριάντα εφτά +CXXXVIII εκατόν τριάντα οχτώ +CXXXIX εκατόν τριάντα εννιά +CXL εκατόν σαράντα +CXLI εκατόν σαράντα ένα +CXLII εκατόν σαράντα δύο +CXLIII εκατόν σαράντα τρία +CXLIV εκατόν σαράντα τέσσερα +CXLV εκατόν σαράντα πέντε +CXLVI εκατόν σαράντα έξι +CXLVII εκατόν σαράντα εφτά +CXLVIII εκατόν σαράντα οχτώ +CXLIX εκατόν σαράντα εννιά +CL εκατόν πενήντα +CLI εκατόν πενήντα ένα +CLII εκατόν πενήντα δύο +CLIII εκατόν πενήντα τρία +CLIV εκατόν πενήντα τέσσερα +CLV εκατόν πενήντα πέντε +CLVI εκατόν πενήντα έξι +CLVII εκατόν πενήντα εφτά +CLVIII εκατόν πενήντα οχτώ +CLIX εκατόν πενήντα εννιά +CLX εκατόν εξήντα +CLXI εκατόν εξήντα ένα +CLXII εκατόν εξήντα δύο +CLXIII εκατόν εξήντα τρία +CLXIV εκατόν εξήντα τέσσερα +CLXV εκατόν εξήντα πέντε +CLXVI εκατόν εξήντα έξι +CLXVII εκατόν εξήντα εφτά +CLXVIII εκατόν εξήντα οχτώ +CLXIX εκατόν εξήντα εννιά +CLXX εκατόν εβδομήντα +CLXXI εκατόν εβδομήντα ένα +CLXXII εκατόν εβδομήντα δύο +CLXXIII εκατόν εβδομήντα τρία +CLXXIV εκατόν εβδομήντα τέσσερα +CLXXV εκατόν εβδομήντα πέντε +CLXXVI εκατόν εβδομήντα έξι +CLXXVII εκατόν εβδομήντα εφτά +CLXXVIII εκατόν εβδομήντα οχτώ +CLXXIX εκατόν εβδομήντα εννιά +CLXXX εκατόν ογδόντα +CLXXXI εκατόν ογδόντα ένα +CLXXXII εκατόν ογδόντα δύο +CLXXXIII εκατόν ογδόντα τρία +CLXXXIV εκατόν ογδόντα τέσσερα +CLXXXV εκατόν ογδόντα πέντε +CLXXXVI εκατόν ογδόντα έξι +CLXXXVII εκατόν ογδόντα εφτά +CLXXXVIII εκατόν ογδόντα οχτώ +CLXXXIX εκατόν ογδόντα εννιά +CXC εκατόν ενενήντα +CXCI εκατόν ενενήντα ένα +CXCII εκατόν ενενήντα δύο +CXCIII εκατόν ενενήντα τρία +CXCIV εκατόν ενενήντα τέσσερα +CXCV εκατόν ενενήντα πέντε +CXCVI εκατόν ενενήντα έξι +CXCVII εκατόν ενενήντα εφτά +CXCVIII εκατόν ενενήντα οχτώ +CXCIX εκατόν ενενήντα εννιά +CC διακόσια +CCI διακόσια ένα +CCII διακόσια δύο +CCIII διακόσια τρία +CCIV διακόσια τέσσερα +CCV διακόσια πέντε +CCVI διακόσια έξι +CCVII διακόσια εφτά +CCVIII διακόσια οχτώ +CCIX διακόσια εννιά +CCX διακόσια δέκα +CCXI διακόσια έντεκα +CCXII διακόσια δώδεκα +CCXIII διακόσια δεκατρία +CCXIV διακόσια δεκατέσσερα +CCXV διακόσια δεκαπέντε +CCXVI διακόσια δεκαέξι +CCXVII διακόσια δεκαεφτά +CCXVIII διακόσια δεκαοχτώ +CCXIX διακόσια δεκαεννιά +CCXX διακόσια είκοσι +CCXXI διακόσια είκοσι ένα +CCXXII διακόσια είκοσι δύο +CCXXIII διακόσια είκοσι τρία +CCXXIV διακόσια είκοσι τέσσερα +CCXXV διακόσια είκοσι πέντε +CCXXVI διακόσια είκοσι έξι +CCXXVII διακόσια είκοσι εφτά +CCXXVIII διακόσια είκοσι οχτώ +CCXXIX διακόσια είκοσι εννιά +CCXXX διακόσια τριάντα +CCXXXI διακόσια τριάντα ένα +CCXXXII διακόσια τριάντα δύο +CCXXXIII διακόσια τριάντα τρία +CCXXXIV διακόσια τριάντα τέσσερα +CCXXXV διακόσια τριάντα πέντε +CCXXXVI διακόσια τριάντα έξι +CCXXXVII διακόσια τριάντα εφτά +CCXXXVIII διακόσια τριάντα οχτώ +CCXXXIX διακόσια τριάντα εννιά +CCXL διακόσια σαράντα +CCXLI διακόσια σαράντα ένα +CCXLII διακόσια σαράντα δύο +CCXLIII διακόσια σαράντα τρία +CCXLIV διακόσια σαράντα τέσσερα +CCXLV διακόσια σαράντα πέντε +CCXLVI διακόσια σαράντα έξι +CCXLVII διακόσια σαράντα εφτά +CCXLVIII διακόσια σαράντα οχτώ +CCXLIX διακόσια σαράντα εννιά +CCL διακόσια πενήντα +CCLI διακόσια πενήντα ένα +CCLII διακόσια πενήντα δύο +CCLIII διακόσια πενήντα τρία +CCLIV διακόσια πενήντα τέσσερα +CCLV διακόσια πενήντα πέντε +CCLVI διακόσια πενήντα έξι +CCLVII διακόσια πενήντα εφτά +CCLVIII διακόσια πενήντα οχτώ +CCLIX διακόσια πενήντα εννιά +CCLX διακόσια εξήντα +CCLXI διακόσια εξήντα ένα +CCLXII διακόσια εξήντα δύο +CCLXIII διακόσια εξήντα τρία +CCLXIV διακόσια εξήντα τέσσερα +CCLXV διακόσια εξήντα πέντε +CCLXVI διακόσια εξήντα έξι +CCLXVII διακόσια εξήντα εφτά +CCLXVIII διακόσια εξήντα οχτώ +CCLXIX διακόσια εξήντα εννιά +CCLXX διακόσια εβδομήντα +CCLXXI διακόσια εβδομήντα ένα +CCLXXII διακόσια εβδομήντα δύο +CCLXXIII διακόσια εβδομήντα τρία +CCLXXIV διακόσια εβδομήντα τέσσερα +CCLXXV διακόσια εβδομήντα πέντε +CCLXXVI διακόσια εβδομήντα έξι +CCLXXVII διακόσια εβδομήντα εφτά +CCLXXVIII διακόσια εβδομήντα οχτώ +CCLXXIX διακόσια εβδομήντα εννιά +CCLXXX διακόσια ογδόντα +CCLXXXI διακόσια ογδόντα ένα +CCLXXXII διακόσια ογδόντα δύο +CCLXXXIII διακόσια ογδόντα τρία +CCLXXXIV διακόσια ογδόντα τέσσερα +CCLXXXV διακόσια ογδόντα πέντε +CCLXXXVI διακόσια ογδόντα έξι +CCLXXXVII διακόσια ογδόντα εφτά +CCLXXXVIII διακόσια ογδόντα οχτώ +CCLXXXIX διακόσια ογδόντα εννιά +CCXC διακόσια ενενήντα +CCXCI διακόσια ενενήντα ένα +CCXCII διακόσια ενενήντα δύο +CCXCIII διακόσια ενενήντα τρία +CCXCIV διακόσια ενενήντα τέσσερα +CCXCV διακόσια ενενήντα πέντε +CCXCVI διακόσια ενενήντα έξι +CCXCVII διακόσια ενενήντα εφτά +CCXCVIII διακόσια ενενήντα οχτώ +CCXCIX διακόσια ενενήντα εννιά +CCC τριακόσια +CCCI τριακόσια ένα +CCCII τριακόσια δύο +CCCIII τριακόσια τρία +CCCIV τριακόσια τέσσερα +CCCV τριακόσια πέντε +CCCVI τριακόσια έξι +CCCVII τριακόσια εφτά +CCCVIII τριακόσια οχτώ +CCCIX τριακόσια εννιά +CCCX τριακόσια δέκα +CCCXI τριακόσια έντεκα +CCCXII τριακόσια δώδεκα +CCCXIII τριακόσια δεκατρία +CCCXIV τριακόσια δεκατέσσερα +CCCXV τριακόσια δεκαπέντε +CCCXVI τριακόσια δεκαέξι +CCCXVII τριακόσια δεκαεφτά +CCCXVIII τριακόσια δεκαοχτώ +CCCXIX τριακόσια δεκαεννιά +CCCXX τριακόσια είκοσι +CCCXXI τριακόσια είκοσι ένα +CCCXXII τριακόσια είκοσι δύο +CCCXXIII τριακόσια είκοσι τρία +CCCXXIV τριακόσια είκοσι τέσσερα +CCCXXV τριακόσια είκοσι πέντε +CCCXXVI τριακόσια είκοσι έξι +CCCXXVII τριακόσια είκοσι εφτά +CCCXXVIII τριακόσια είκοσι οχτώ +CCCXXIX τριακόσια είκοσι εννιά +CCCXXX τριακόσια τριάντα +CCCXXXI τριακόσια τριάντα ένα +CCCXXXII τριακόσια τριάντα δύο +CCCXXXIII τριακόσια τριάντα τρία +CCCXXXIV τριακόσια τριάντα τέσσερα +CCCXXXV τριακόσια τριάντα πέντε +CCCXXXVI τριακόσια τριάντα έξι +CCCXXXVII τριακόσια τριάντα εφτά +CCCXXXVIII τριακόσια τριάντα οχτώ +CCCXXXIX τριακόσια τριάντα εννιά +CCCXL τριακόσια σαράντα +CCCXLI τριακόσια σαράντα ένα +CCCXLII τριακόσια σαράντα δύο +CCCXLIII τριακόσια σαράντα τρία +CCCXLIV τριακόσια σαράντα τέσσερα +CCCXLV τριακόσια σαράντα πέντε +CCCXLVI τριακόσια σαράντα έξι +CCCXLVII τριακόσια σαράντα εφτά +CCCXLVIII τριακόσια σαράντα οχτώ +CCCXLIX τριακόσια σαράντα εννιά +CCCL τριακόσια πενήντα +CCCLI τριακόσια πενήντα ένα +CCCLII τριακόσια πενήντα δύο +CCCLIII τριακόσια πενήντα τρία +CCCLIV τριακόσια πενήντα τέσσερα +CCCLV τριακόσια πενήντα πέντε +CCCLVI τριακόσια πενήντα έξι +CCCLVII τριακόσια πενήντα εφτά +CCCLVIII τριακόσια πενήντα οχτώ +CCCLIX τριακόσια πενήντα εννιά +CCCLX τριακόσια εξήντα +CCCLXI τριακόσια εξήντα ένα +CCCLXII τριακόσια εξήντα δύο +CCCLXIII τριακόσια εξήντα τρία +CCCLXIV τριακόσια εξήντα τέσσερα +CCCLXV τριακόσια εξήντα πέντε +CCCLXVI τριακόσια εξήντα έξι +CCCLXVII τριακόσια εξήντα εφτά +CCCLXVIII τριακόσια εξήντα οχτώ +CCCLXIX τριακόσια εξήντα εννιά +CCCLXX τριακόσια εβδομήντα +CCCLXXI τριακόσια εβδομήντα ένα +CCCLXXII τριακόσια εβδομήντα δύο +CCCLXXIII τριακόσια εβδομήντα τρία +CCCLXXIV τριακόσια εβδομήντα τέσσερα +CCCLXXV τριακόσια εβδομήντα πέντε +CCCLXXVI τριακόσια εβδομήντα έξι +CCCLXXVII τριακόσια εβδομήντα εφτά +CCCLXXVIII τριακόσια εβδομήντα οχτώ +CCCLXXIX τριακόσια εβδομήντα εννιά +CCCLXXX τριακόσια ογδόντα +CCCLXXXI τριακόσια ογδόντα ένα +CCCLXXXII τριακόσια ογδόντα δύο +CCCLXXXIII τριακόσια ογδόντα τρία +CCCLXXXIV τριακόσια ογδόντα τέσσερα +CCCLXXXV τριακόσια ογδόντα πέντε +CCCLXXXVI τριακόσια ογδόντα έξι +CCCLXXXVII τριακόσια ογδόντα εφτά +CCCLXXXVIII τριακόσια ογδόντα οχτώ +CCCLXXXIX τριακόσια ογδόντα εννιά +CCCXC τριακόσια ενενήντα +CCCXCI τριακόσια ενενήντα ένα +CCCXCII τριακόσια ενενήντα δύο +CCCXCIII τριακόσια ενενήντα τρία +CCCXCIV τριακόσια ενενήντα τέσσερα +CCCXCV τριακόσια ενενήντα πέντε +CCCXCVI τριακόσια ενενήντα έξι +CCCXCVII τριακόσια ενενήντα εφτά +CCCXCVIII τριακόσια ενενήντα οχτώ +CCCXCIX τριακόσια ενενήντα εννιά +CD τετρακόσια +CDI τετρακόσια ένα +CDII τετρακόσια δύο +CDIII τετρακόσια τρία +CDIV τετρακόσια τέσσερα +CDV τετρακόσια πέντε +CDVI τετρακόσια έξι +CDVII τετρακόσια εφτά +CDVIII τετρακόσια οχτώ +CDIX τετρακόσια εννιά +CDX τετρακόσια δέκα +CDXI τετρακόσια έντεκα +CDXII τετρακόσια δώδεκα +CDXIII τετρακόσια δεκατρία +CDXIV τετρακόσια δεκατέσσερα +CDXV τετρακόσια δεκαπέντε +CDXVI τετρακόσια δεκαέξι +CDXVII τετρακόσια δεκαεφτά +CDXVIII τετρακόσια δεκαοχτώ +CDXIX τετρακόσια δεκαεννιά +CDXX τετρακόσια είκοσι +CDXXI τετρακόσια είκοσι ένα +CDXXII τετρακόσια είκοσι δύο +CDXXIII τετρακόσια είκοσι τρία +CDXXIV τετρακόσια είκοσι τέσσερα +CDXXV τετρακόσια είκοσι πέντε +CDXXVI τετρακόσια είκοσι έξι +CDXXVII τετρακόσια είκοσι εφτά +CDXXVIII τετρακόσια είκοσι οχτώ +CDXXIX τετρακόσια είκοσι εννιά +CDXXX τετρακόσια τριάντα +CDXXXI τετρακόσια τριάντα ένα +CDXXXII τετρακόσια τριάντα δύο +CDXXXIII τετρακόσια τριάντα τρία +CDXXXIV τετρακόσια τριάντα τέσσερα +CDXXXV τετρακόσια τριάντα πέντε +CDXXXVI τετρακόσια τριάντα έξι +CDXXXVII τετρακόσια τριάντα εφτά +CDXXXVIII τετρακόσια τριάντα οχτώ +CDXXXIX τετρακόσια τριάντα εννιά +CDXL τετρακόσια σαράντα +CDXLI τετρακόσια σαράντα ένα +CDXLII τετρακόσια σαράντα δύο +CDXLIII τετρακόσια σαράντα τρία +CDXLIV τετρακόσια σαράντα τέσσερα +CDXLV τετρακόσια σαράντα πέντε +CDXLVI τετρακόσια σαράντα έξι +CDXLVII τετρακόσια σαράντα εφτά +CDXLVIII τετρακόσια σαράντα οχτώ +CDXLIX τετρακόσια σαράντα εννιά +CDL τετρακόσια πενήντα +CDLI τετρακόσια πενήντα ένα +CDLII τετρακόσια πενήντα δύο +CDLIII τετρακόσια πενήντα τρία +CDLIV τετρακόσια πενήντα τέσσερα +CDLV τετρακόσια πενήντα πέντε +CDLVI τετρακόσια πενήντα έξι +CDLVII τετρακόσια πενήντα εφτά +CDLVIII τετρακόσια πενήντα οχτώ +CDLIX τετρακόσια πενήντα εννιά +CDLX τετρακόσια εξήντα +CDLXI τετρακόσια εξήντα ένα +CDLXII τετρακόσια εξήντα δύο +CDLXIII τετρακόσια εξήντα τρία +CDLXIV τετρακόσια εξήντα τέσσερα +CDLXV τετρακόσια εξήντα πέντε +CDLXVI τετρακόσια εξήντα έξι +CDLXVII τετρακόσια εξήντα εφτά +CDLXVIII τετρακόσια εξήντα οχτώ +CDLXIX τετρακόσια εξήντα εννιά +CDLXX τετρακόσια εβδομήντα +CDLXXI τετρακόσια εβδομήντα ένα +CDLXXII τετρακόσια εβδομήντα δύο +CDLXXIII τετρακόσια εβδομήντα τρία +CDLXXIV τετρακόσια εβδομήντα τέσσερα +CDLXXV τετρακόσια εβδομήντα πέντε +CDLXXVI τετρακόσια εβδομήντα έξι +CDLXXVII τετρακόσια εβδομήντα εφτά +CDLXXVIII τετρακόσια εβδομήντα οχτώ +CDLXXIX τετρακόσια εβδομήντα εννιά +CDLXXX τετρακόσια ογδόντα +CDLXXXI τετρακόσια ογδόντα ένα +CDLXXXII τετρακόσια ογδόντα δύο +CDLXXXIII τετρακόσια ογδόντα τρία +CDLXXXIV τετρακόσια ογδόντα τέσσερα +CDLXXXV τετρακόσια ογδόντα πέντε +CDLXXXVI τετρακόσια ογδόντα έξι +CDLXXXVII τετρακόσια ογδόντα εφτά +CDLXXXVIII τετρακόσια ογδόντα οχτώ +CDLXXXIX τετρακόσια ογδόντα εννιά +CDXC τετρακόσια ενενήντα +CDXCI τετρακόσια ενενήντα ένα +CDXCII τετρακόσια ενενήντα δύο +CDXCIII τετρακόσια ενενήντα τρία +CDXCIV τετρακόσια ενενήντα τέσσερα +CDXCV τετρακόσια ενενήντα πέντε +CDXCVI τετρακόσια ενενήντα έξι +CDXCVII τετρακόσια ενενήντα εφτά +CDXCVIII τετρακόσια ενενήντα οχτώ +CDXCIX τετρακόσια ενενήντα εννιά +D πεντακόσια +DI πεντακόσια ένα +DII πεντακόσια δύο +DIII πεντακόσια τρία +DIV πεντακόσια τέσσερα +DV πεντακόσια πέντε +DVI πεντακόσια έξι +DVII πεντακόσια εφτά +DVIII πεντακόσια οχτώ +DIX πεντακόσια εννιά +DX πεντακόσια δέκα +DXI πεντακόσια έντεκα +DXII πεντακόσια δώδεκα +DXIII πεντακόσια δεκατρία +DXIV πεντακόσια δεκατέσσερα +DXV πεντακόσια δεκαπέντε +DXVI πεντακόσια δεκαέξι +DXVII πεντακόσια δεκαεφτά +DXVIII πεντακόσια δεκαοχτώ +DXIX πεντακόσια δεκαεννιά +DXX πεντακόσια είκοσι +DXXI πεντακόσια είκοσι ένα +DXXII πεντακόσια είκοσι δύο +DXXIII πεντακόσια είκοσι τρία +DXXIV πεντακόσια είκοσι τέσσερα +DXXV πεντακόσια είκοσι πέντε +DXXVI πεντακόσια είκοσι έξι +DXXVII πεντακόσια είκοσι εφτά +DXXVIII πεντακόσια είκοσι οχτώ +DXXIX πεντακόσια είκοσι εννιά +DXXX πεντακόσια τριάντα +DXXXI πεντακόσια τριάντα ένα +DXXXII πεντακόσια τριάντα δύο +DXXXIII πεντακόσια τριάντα τρία +DXXXIV πεντακόσια τριάντα τέσσερα +DXXXV πεντακόσια τριάντα πέντε +DXXXVI πεντακόσια τριάντα έξι +DXXXVII πεντακόσια τριάντα εφτά +DXXXVIII πεντακόσια τριάντα οχτώ +DXXXIX πεντακόσια τριάντα εννιά +DXL πεντακόσια σαράντα +DXLI πεντακόσια σαράντα ένα +DXLII πεντακόσια σαράντα δύο +DXLIII πεντακόσια σαράντα τρία +DXLIV πεντακόσια σαράντα τέσσερα +DXLV πεντακόσια σαράντα πέντε +DXLVI πεντακόσια σαράντα έξι +DXLVII πεντακόσια σαράντα εφτά +DXLVIII πεντακόσια σαράντα οχτώ +DXLIX πεντακόσια σαράντα εννιά +DL πεντακόσια πενήντα +DLI πεντακόσια πενήντα ένα +DLII πεντακόσια πενήντα δύο +DLIII πεντακόσια πενήντα τρία +DLIV πεντακόσια πενήντα τέσσερα +DLV πεντακόσια πενήντα πέντε +DLVI πεντακόσια πενήντα έξι +DLVII πεντακόσια πενήντα εφτά +DLVIII πεντακόσια πενήντα οχτώ +DLIX πεντακόσια πενήντα εννιά +DLX πεντακόσια εξήντα +DLXI πεντακόσια εξήντα ένα +DLXII πεντακόσια εξήντα δύο +DLXIII πεντακόσια εξήντα τρία +DLXIV πεντακόσια εξήντα τέσσερα +DLXV πεντακόσια εξήντα πέντε +DLXVI πεντακόσια εξήντα έξι +DLXVII πεντακόσια εξήντα εφτά +DLXVIII πεντακόσια εξήντα οχτώ +DLXIX πεντακόσια εξήντα εννιά +DLXX πεντακόσια εβδομήντα +DLXXI πεντακόσια εβδομήντα ένα +DLXXII πεντακόσια εβδομήντα δύο +DLXXIII πεντακόσια εβδομήντα τρία +DLXXIV πεντακόσια εβδομήντα τέσσερα +DLXXV πεντακόσια εβδομήντα πέντε +DLXXVI πεντακόσια εβδομήντα έξι +DLXXVII πεντακόσια εβδομήντα εφτά +DLXXVIII πεντακόσια εβδομήντα οχτώ +DLXXIX πεντακόσια εβδομήντα εννιά +DLXXX πεντακόσια ογδόντα +DLXXXI πεντακόσια ογδόντα ένα +DLXXXII πεντακόσια ογδόντα δύο +DLXXXIII πεντακόσια ογδόντα τρία +DLXXXIV πεντακόσια ογδόντα τέσσερα +DLXXXV πεντακόσια ογδόντα πέντε +DLXXXVI πεντακόσια ογδόντα έξι +DLXXXVII πεντακόσια ογδόντα εφτά +DLXXXVIII πεντακόσια ογδόντα οχτώ +DLXXXIX πεντακόσια ογδόντα εννιά +DXC πεντακόσια ενενήντα +DXCI πεντακόσια ενενήντα ένα +DXCII πεντακόσια ενενήντα δύο +DXCIII πεντακόσια ενενήντα τρία +DXCIV πεντακόσια ενενήντα τέσσερα +DXCV πεντακόσια ενενήντα πέντε +DXCVI πεντακόσια ενενήντα έξι +DXCVII πεντακόσια ενενήντα εφτά +DXCVIII πεντακόσια ενενήντα οχτώ +DXCIX πεντακόσια ενενήντα εννιά +DC εξακόσια +DCI εξακόσια ένα +DCII εξακόσια δύο +DCIII εξακόσια τρία +DCIV εξακόσια τέσσερα +DCV εξακόσια πέντε +DCVI εξακόσια έξι +DCVII εξακόσια εφτά +DCVIII εξακόσια οχτώ +DCIX εξακόσια εννιά +DCX εξακόσια δέκα +DCXI εξακόσια έντεκα +DCXII εξακόσια δώδεκα +DCXIII εξακόσια δεκατρία +DCXIV εξακόσια δεκατέσσερα +DCXV εξακόσια δεκαπέντε +DCXVI εξακόσια δεκαέξι +DCXVII εξακόσια δεκαεφτά +DCXVIII εξακόσια δεκαοχτώ +DCXIX εξακόσια δεκαεννιά +DCXX εξακόσια είκοσι +DCXXI εξακόσια είκοσι ένα +DCXXII εξακόσια είκοσι δύο +DCXXIII εξακόσια είκοσι τρία +DCXXIV εξακόσια είκοσι τέσσερα +DCXXV εξακόσια είκοσι πέντε +DCXXVI εξακόσια είκοσι έξι +DCXXVII εξακόσια είκοσι εφτά +DCXXVIII εξακόσια είκοσι οχτώ +DCXXIX εξακόσια είκοσι εννιά +DCXXX εξακόσια τριάντα +DCXXXI εξακόσια τριάντα ένα +DCXXXII εξακόσια τριάντα δύο +DCXXXIII εξακόσια τριάντα τρία +DCXXXIV εξακόσια τριάντα τέσσερα +DCXXXV εξακόσια τριάντα πέντε +DCXXXVI εξακόσια τριάντα έξι +DCXXXVII εξακόσια τριάντα εφτά +DCXXXVIII εξακόσια τριάντα οχτώ +DCXXXIX εξακόσια τριάντα εννιά +DCXL εξακόσια σαράντα +DCXLI εξακόσια σαράντα ένα +DCXLII εξακόσια σαράντα δύο +DCXLIII εξακόσια σαράντα τρία +DCXLIV εξακόσια σαράντα τέσσερα +DCXLV εξακόσια σαράντα πέντε +DCXLVI εξακόσια σαράντα έξι +DCXLVII εξακόσια σαράντα εφτά +DCXLVIII εξακόσια σαράντα οχτώ +DCXLIX εξακόσια σαράντα εννιά +DCL εξακόσια πενήντα +DCLI εξακόσια πενήντα ένα +DCLII εξακόσια πενήντα δύο +DCLIII εξακόσια πενήντα τρία +DCLIV εξακόσια πενήντα τέσσερα +DCLV εξακόσια πενήντα πέντε +DCLVI εξακόσια πενήντα έξι +DCLVII εξακόσια πενήντα εφτά +DCLVIII εξακόσια πενήντα οχτώ +DCLIX εξακόσια πενήντα εννιά +DCLX εξακόσια εξήντα +DCLXI εξακόσια εξήντα ένα +DCLXII εξακόσια εξήντα δύο +DCLXIII εξακόσια εξήντα τρία +DCLXIV εξακόσια εξήντα τέσσερα +DCLXV εξακόσια εξήντα πέντε +DCLXVI εξακόσια εξήντα έξι +DCLXVII εξακόσια εξήντα εφτά +DCLXVIII εξακόσια εξήντα οχτώ +DCLXIX εξακόσια εξήντα εννιά +DCLXX εξακόσια εβδομήντα +DCLXXI εξακόσια εβδομήντα ένα +DCLXXII εξακόσια εβδομήντα δύο +DCLXXIII εξακόσια εβδομήντα τρία +DCLXXIV εξακόσια εβδομήντα τέσσερα +DCLXXV εξακόσια εβδομήντα πέντε +DCLXXVI εξακόσια εβδομήντα έξι +DCLXXVII εξακόσια εβδομήντα εφτά +DCLXXVIII εξακόσια εβδομήντα οχτώ +DCLXXIX εξακόσια εβδομήντα εννιά +DCLXXX εξακόσια ογδόντα +DCLXXXI εξακόσια ογδόντα ένα +DCLXXXII εξακόσια ογδόντα δύο +DCLXXXIII εξακόσια ογδόντα τρία +DCLXXXIV εξακόσια ογδόντα τέσσερα +DCLXXXV εξακόσια ογδόντα πέντε +DCLXXXVI εξακόσια ογδόντα έξι +DCLXXXVII εξακόσια ογδόντα εφτά +DCLXXXVIII εξακόσια ογδόντα οχτώ +DCLXXXIX εξακόσια ογδόντα εννιά +DCXC εξακόσια ενενήντα +DCXCI εξακόσια ενενήντα ένα +DCXCII εξακόσια ενενήντα δύο +DCXCIII εξακόσια ενενήντα τρία +DCXCIV εξακόσια ενενήντα τέσσερα +DCXCV εξακόσια ενενήντα πέντε +DCXCVI εξακόσια ενενήντα έξι +DCXCVII εξακόσια ενενήντα εφτά +DCXCVIII εξακόσια ενενήντα οχτώ +DCXCIX εξακόσια ενενήντα εννιά +DCC εφτακόσια +DCCI εφτακόσια ένα +DCCII εφτακόσια δύο +DCCIII εφτακόσια τρία +DCCIV εφτακόσια τέσσερα +DCCV εφτακόσια πέντε +DCCVI εφτακόσια έξι +DCCVII εφτακόσια εφτά +DCCVIII εφτακόσια οχτώ +DCCIX εφτακόσια εννιά +DCCX εφτακόσια δέκα +DCCXI εφτακόσια έντεκα +DCCXII εφτακόσια δώδεκα +DCCXIII εφτακόσια δεκατρία +DCCXIV εφτακόσια δεκατέσσερα +DCCXV εφτακόσια δεκαπέντε +DCCXVI εφτακόσια δεκαέξι +DCCXVII εφτακόσια δεκαεφτά +DCCXVIII εφτακόσια δεκαοχτώ +DCCXIX εφτακόσια δεκαεννιά +DCCXX εφτακόσια είκοσι +DCCXXI εφτακόσια είκοσι ένα +DCCXXII εφτακόσια είκοσι δύο +DCCXXIII εφτακόσια είκοσι τρία +DCCXXIV εφτακόσια είκοσι τέσσερα +DCCXXV εφτακόσια είκοσι πέντε +DCCXXVI εφτακόσια είκοσι έξι +DCCXXVII εφτακόσια είκοσι εφτά +DCCXXVIII εφτακόσια είκοσι οχτώ +DCCXXIX εφτακόσια είκοσι εννιά +DCCXXX εφτακόσια τριάντα +DCCXXXI εφτακόσια τριάντα ένα +DCCXXXII εφτακόσια τριάντα δύο +DCCXXXIII εφτακόσια τριάντα τρία +DCCXXXIV εφτακόσια τριάντα τέσσερα +DCCXXXV εφτακόσια τριάντα πέντε +DCCXXXVI εφτακόσια τριάντα έξι +DCCXXXVII εφτακόσια τριάντα εφτά +DCCXXXVIII εφτακόσια τριάντα οχτώ +DCCXXXIX εφτακόσια τριάντα εννιά +DCCXL εφτακόσια σαράντα +DCCXLI εφτακόσια σαράντα ένα +DCCXLII εφτακόσια σαράντα δύο +DCCXLIII εφτακόσια σαράντα τρία +DCCXLIV εφτακόσια σαράντα τέσσερα +DCCXLV εφτακόσια σαράντα πέντε +DCCXLVI εφτακόσια σαράντα έξι +DCCXLVII εφτακόσια σαράντα εφτά +DCCXLVIII εφτακόσια σαράντα οχτώ +DCCXLIX εφτακόσια σαράντα εννιά +DCCL εφτακόσια πενήντα +DCCLI εφτακόσια πενήντα ένα +DCCLII εφτακόσια πενήντα δύο +DCCLIII εφτακόσια πενήντα τρία +DCCLIV εφτακόσια πενήντα τέσσερα +DCCLV εφτακόσια πενήντα πέντε +DCCLVI εφτακόσια πενήντα έξι +DCCLVII εφτακόσια πενήντα εφτά +DCCLVIII εφτακόσια πενήντα οχτώ +DCCLIX εφτακόσια πενήντα εννιά +DCCLX εφτακόσια εξήντα +DCCLXI εφτακόσια εξήντα ένα +DCCLXII εφτακόσια εξήντα δύο +DCCLXIII εφτακόσια εξήντα τρία +DCCLXIV εφτακόσια εξήντα τέσσερα +DCCLXV εφτακόσια εξήντα πέντε +DCCLXVI εφτακόσια εξήντα έξι +DCCLXVII εφτακόσια εξήντα εφτά +DCCLXVIII εφτακόσια εξήντα οχτώ +DCCLXIX εφτακόσια εξήντα εννιά +DCCLXX εφτακόσια εβδομήντα +DCCLXXI εφτακόσια εβδομήντα ένα +DCCLXXII εφτακόσια εβδομήντα δύο +DCCLXXIII εφτακόσια εβδομήντα τρία +DCCLXXIV εφτακόσια εβδομήντα τέσσερα +DCCLXXV εφτακόσια εβδομήντα πέντε +DCCLXXVI εφτακόσια εβδομήντα έξι +DCCLXXVII εφτακόσια εβδομήντα εφτά +DCCLXXVIII εφτακόσια εβδομήντα οχτώ +DCCLXXIX εφτακόσια εβδομήντα εννιά +DCCLXXX εφτακόσια ογδόντα +DCCLXXXI εφτακόσια ογδόντα ένα +DCCLXXXII εφτακόσια ογδόντα δύο +DCCLXXXIII εφτακόσια ογδόντα τρία +DCCLXXXIV εφτακόσια ογδόντα τέσσερα +DCCLXXXV εφτακόσια ογδόντα πέντε +DCCLXXXVI εφτακόσια ογδόντα έξι +DCCLXXXVII εφτακόσια ογδόντα εφτά +DCCLXXXVIII εφτακόσια ογδόντα οχτώ +DCCLXXXIX εφτακόσια ογδόντα εννιά +DCCXC εφτακόσια ενενήντα +DCCXCI εφτακόσια ενενήντα ένα +DCCXCII εφτακόσια ενενήντα δύο +DCCXCIII εφτακόσια ενενήντα τρία +DCCXCIV εφτακόσια ενενήντα τέσσερα +DCCXCV εφτακόσια ενενήντα πέντε +DCCXCVI εφτακόσια ενενήντα έξι +DCCXCVII εφτακόσια ενενήντα εφτά +DCCXCVIII εφτακόσια ενενήντα οχτώ +DCCXCIX εφτακόσια ενενήντα εννιά +DCCC οχτακόσια +DCCCI οχτακόσια ένα +DCCCII οχτακόσια δύο +DCCCIII οχτακόσια τρία +DCCCIV οχτακόσια τέσσερα +DCCCV οχτακόσια πέντε +DCCCVI οχτακόσια έξι +DCCCVII οχτακόσια εφτά +DCCCVIII οχτακόσια οχτώ +DCCCIX οχτακόσια εννιά +DCCCX οχτακόσια δέκα +DCCCXI οχτακόσια έντεκα +DCCCXII οχτακόσια δώδεκα +DCCCXIII οχτακόσια δεκατρία +DCCCXIV οχτακόσια δεκατέσσερα +DCCCXV οχτακόσια δεκαπέντε +DCCCXVI οχτακόσια δεκαέξι +DCCCXVII οχτακόσια δεκαεφτά +DCCCXVIII οχτακόσια δεκαοχτώ +DCCCXIX οχτακόσια δεκαεννιά +DCCCXX οχτακόσια είκοσι +DCCCXXI οχτακόσια είκοσι ένα +DCCCXXII οχτακόσια είκοσι δύο +DCCCXXIII οχτακόσια είκοσι τρία +DCCCXXIV οχτακόσια είκοσι τέσσερα +DCCCXXV οχτακόσια είκοσι πέντε +DCCCXXVI οχτακόσια είκοσι έξι +DCCCXXVII οχτακόσια είκοσι εφτά +DCCCXXVIII οχτακόσια είκοσι οχτώ +DCCCXXIX οχτακόσια είκοσι εννιά +DCCCXXX οχτακόσια τριάντα +DCCCXXXI οχτακόσια τριάντα ένα +DCCCXXXII οχτακόσια τριάντα δύο +DCCCXXXIII οχτακόσια τριάντα τρία +DCCCXXXIV οχτακόσια τριάντα τέσσερα +DCCCXXXV οχτακόσια τριάντα πέντε +DCCCXXXVI οχτακόσια τριάντα έξι +DCCCXXXVII οχτακόσια τριάντα εφτά +DCCCXXXVIII οχτακόσια τριάντα οχτώ +DCCCXXXIX οχτακόσια τριάντα εννιά +DCCCXL οχτακόσια σαράντα +DCCCXLI οχτακόσια σαράντα ένα +DCCCXLII οχτακόσια σαράντα δύο +DCCCXLIII οχτακόσια σαράντα τρία +DCCCXLIV οχτακόσια σαράντα τέσσερα +DCCCXLV οχτακόσια σαράντα πέντε +DCCCXLVI οχτακόσια σαράντα έξι +DCCCXLVII οχτακόσια σαράντα εφτά +DCCCXLVIII οχτακόσια σαράντα οχτώ +DCCCXLIX οχτακόσια σαράντα εννιά +DCCCL οχτακόσια πενήντα +DCCCLI οχτακόσια πενήντα ένα +DCCCLII οχτακόσια πενήντα δύο +DCCCLIII οχτακόσια πενήντα τρία +DCCCLIV οχτακόσια πενήντα τέσσερα +DCCCLV οχτακόσια πενήντα πέντε +DCCCLVI οχτακόσια πενήντα έξι +DCCCLVII οχτακόσια πενήντα εφτά +DCCCLVIII οχτακόσια πενήντα οχτώ +DCCCLIX οχτακόσια πενήντα εννιά +DCCCLX οχτακόσια εξήντα +DCCCLXI οχτακόσια εξήντα ένα +DCCCLXII οχτακόσια εξήντα δύο +DCCCLXIII οχτακόσια εξήντα τρία +DCCCLXIV οχτακόσια εξήντα τέσσερα +DCCCLXV οχτακόσια εξήντα πέντε +DCCCLXVI οχτακόσια εξήντα έξι +DCCCLXVII οχτακόσια εξήντα εφτά +DCCCLXVIII οχτακόσια εξήντα οχτώ +DCCCLXIX οχτακόσια εξήντα εννιά +DCCCLXX οχτακόσια εβδομήντα +DCCCLXXI οχτακόσια εβδομήντα ένα +DCCCLXXII οχτακόσια εβδομήντα δύο +DCCCLXXIII οχτακόσια εβδομήντα τρία +DCCCLXXIV οχτακόσια εβδομήντα τέσσερα +DCCCLXXV οχτακόσια εβδομήντα πέντε +DCCCLXXVI οχτακόσια εβδομήντα έξι +DCCCLXXVII οχτακόσια εβδομήντα εφτά +DCCCLXXVIII οχτακόσια εβδομήντα οχτώ +DCCCLXXIX οχτακόσια εβδομήντα εννιά +DCCCLXXX οχτακόσια ογδόντα +DCCCLXXXI οχτακόσια ογδόντα ένα +DCCCLXXXII οχτακόσια ογδόντα δύο +DCCCLXXXIII οχτακόσια ογδόντα τρία +DCCCLXXXIV οχτακόσια ογδόντα τέσσερα +DCCCLXXXV οχτακόσια ογδόντα πέντε +DCCCLXXXVI οχτακόσια ογδόντα έξι +DCCCLXXXVII οχτακόσια ογδόντα εφτά +DCCCLXXXVIII οχτακόσια ογδόντα οχτώ +DCCCLXXXIX οχτακόσια ογδόντα εννιά +DCCCXC οχτακόσια ενενήντα +DCCCXCI οχτακόσια ενενήντα ένα +DCCCXCII οχτακόσια ενενήντα δύο +DCCCXCIII οχτακόσια ενενήντα τρία +DCCCXCIV οχτακόσια ενενήντα τέσσερα +DCCCXCV οχτακόσια ενενήντα πέντε +DCCCXCVI οχτακόσια ενενήντα έξι +DCCCXCVII οχτακόσια ενενήντα εφτά +DCCCXCVIII οχτακόσια ενενήντα οχτώ +DCCCXCIX οχτακόσια ενενήντα εννιά +CM εννιακόσια +CMI εννιακόσια ένα +CMII εννιακόσια δύο +CMIII εννιακόσια τρία +CMIV εννιακόσια τέσσερα +CMV εννιακόσια πέντε +CMVI εννιακόσια έξι +CMVII εννιακόσια εφτά +CMVIII εννιακόσια οχτώ +CMIX εννιακόσια εννιά +CMX εννιακόσια δέκα +CMXI εννιακόσια έντεκα +CMXII εννιακόσια δώδεκα +CMXIII εννιακόσια δεκατρία +CMXIV εννιακόσια δεκατέσσερα +CMXV εννιακόσια δεκαπέντε +CMXVI εννιακόσια δεκαέξι +CMXVII εννιακόσια δεκαεφτά +CMXVIII εννιακόσια δεκαοχτώ +CMXIX εννιακόσια δεκαεννιά +CMXX εννιακόσια είκοσι +CMXXI εννιακόσια είκοσι ένα +CMXXII εννιακόσια είκοσι δύο +CMXXIII εννιακόσια είκοσι τρία +CMXXIV εννιακόσια είκοσι τέσσερα +CMXXV εννιακόσια είκοσι πέντε +CMXXVI εννιακόσια είκοσι έξι +CMXXVII εννιακόσια είκοσι εφτά +CMXXVIII εννιακόσια είκοσι οχτώ +CMXXIX εννιακόσια είκοσι εννιά +CMXXX εννιακόσια τριάντα +CMXXXI εννιακόσια τριάντα ένα +CMXXXII εννιακόσια τριάντα δύο +CMXXXIII εννιακόσια τριάντα τρία +CMXXXIV εννιακόσια τριάντα τέσσερα +CMXXXV εννιακόσια τριάντα πέντε +CMXXXVI εννιακόσια τριάντα έξι +CMXXXVII εννιακόσια τριάντα εφτά +CMXXXVIII εννιακόσια τριάντα οχτώ +CMXXXIX εννιακόσια τριάντα εννιά +CMXL εννιακόσια σαράντα +CMXLI εννιακόσια σαράντα ένα +CMXLII εννιακόσια σαράντα δύο +CMXLIII εννιακόσια σαράντα τρία +CMXLIV εννιακόσια σαράντα τέσσερα +CMXLV εννιακόσια σαράντα πέντε +CMXLVI εννιακόσια σαράντα έξι +CMXLVII εννιακόσια σαράντα εφτά +CMXLVIII εννιακόσια σαράντα οχτώ +CMXLIX εννιακόσια σαράντα εννιά +CML εννιακόσια πενήντα +CMLI εννιακόσια πενήντα ένα +CMLII εννιακόσια πενήντα δύο +CMLIII εννιακόσια πενήντα τρία +CMLIV εννιακόσια πενήντα τέσσερα +CMLV εννιακόσια πενήντα πέντε +CMLVI εννιακόσια πενήντα έξι +CMLVII εννιακόσια πενήντα εφτά +CMLVIII εννιακόσια πενήντα οχτώ +CMLIX εννιακόσια πενήντα εννιά +CMLX εννιακόσια εξήντα +CMLXI εννιακόσια εξήντα ένα +CMLXII εννιακόσια εξήντα δύο +CMLXIII εννιακόσια εξήντα τρία +CMLXIV εννιακόσια εξήντα τέσσερα +CMLXV εννιακόσια εξήντα πέντε +CMLXVI εννιακόσια εξήντα έξι +CMLXVII εννιακόσια εξήντα εφτά +CMLXVIII εννιακόσια εξήντα οχτώ +CMLXIX εννιακόσια εξήντα εννιά +CMLXX εννιακόσια εβδομήντα +CMLXXI εννιακόσια εβδομήντα ένα +CMLXXII εννιακόσια εβδομήντα δύο +CMLXXIII εννιακόσια εβδομήντα τρία +CMLXXIV εννιακόσια εβδομήντα τέσσερα +CMLXXV εννιακόσια εβδομήντα πέντε +CMLXXVI εννιακόσια εβδομήντα έξι +CMLXXVII εννιακόσια εβδομήντα εφτά +CMLXXVIII εννιακόσια εβδομήντα οχτώ +CMLXXIX εννιακόσια εβδομήντα εννιά +CMLXXX εννιακόσια ογδόντα +CMLXXXI εννιακόσια ογδόντα ένα +CMLXXXII εννιακόσια ογδόντα δύο +CMLXXXIII εννιακόσια ογδόντα τρία +CMLXXXIV εννιακόσια ογδόντα τέσσερα +CMLXXXV εννιακόσια ογδόντα πέντε +CMLXXXVI εννιακόσια ογδόντα έξι +CMLXXXVII εννιακόσια ογδόντα εφτά +CMLXXXVIII εννιακόσια ογδόντα οχτώ +CMLXXXIX εννιακόσια ογδόντα εννιά +CMXC εννιακόσια ενενήντα +CMXCI εννιακόσια ενενήντα ένα +CMXCII εννιακόσια ενενήντα δύο +CMXCIII εννιακόσια ενενήντα τρία +CMXCIV εννιακόσια ενενήντα τέσσερα +CMXCV εννιακόσια ενενήντα πέντε +CMXCVI εννιακόσια ενενήντα έξι +CMXCVII εννιακόσια ενενήντα εφτά +CMXCVIII εννιακόσια ενενήντα οχτώ +CMXCIX εννιακόσια ενενήντα εννιά +M χίλια +MI χίλια ένα +MII χίλια δύο +MIII χίλια τρία +MIV χίλια τέσσερα +MV χίλια πέντε +MVI χίλια έξι +MVII χίλια εφτά +MVIII χίλια οχτώ +MIX χίλια εννιά +MX χίλια δέκα +MXI χίλια έντεκα +MXII χίλια δώδεκα +MXIII χίλια δεκατρία +MXIV χίλια δεκατέσσερα +MXV χίλια δεκαπέντε +MXVI χίλια δεκαέξι +MXVII χίλια δεκαεφτά +MXVIII χίλια δεκαοχτώ +MXIX χίλια δεκαεννιά +MXX χίλια είκοσι +MXXI χίλια είκοσι ένα +MXXII χίλια είκοσι δύο +MXXIII χίλια είκοσι τρία +MXXIV χίλια είκοσι τέσσερα +MXXV χίλια είκοσι πέντε +MXXVI χίλια είκοσι έξι +MXXVII χίλια είκοσι εφτά +MXXVIII χίλια είκοσι οχτώ +MXXIX χίλια είκοσι εννιά +MXXX χίλια τριάντα +MXXXI χίλια τριάντα ένα +MXXXII χίλια τριάντα δύο +MXXXIII χίλια τριάντα τρία +MXXXIV χίλια τριάντα τέσσερα +MXXXV χίλια τριάντα πέντε +MXXXVI χίλια τριάντα έξι +MXXXVII χίλια τριάντα εφτά +MXXXVIII χίλια τριάντα οχτώ +MXXXIX χίλια τριάντα εννιά +MXL χίλια σαράντα +MXLI χίλια σαράντα ένα +MXLII χίλια σαράντα δύο +MXLIII χίλια σαράντα τρία +MXLIV χίλια σαράντα τέσσερα +MXLV χίλια σαράντα πέντε +MXLVI χίλια σαράντα έξι +MXLVII χίλια σαράντα εφτά +MXLVIII χίλια σαράντα οχτώ +MXLIX χίλια σαράντα εννιά +ML χίλια πενήντα +MLI χίλια πενήντα ένα +MLII χίλια πενήντα δύο +MLIII χίλια πενήντα τρία +MLIV χίλια πενήντα τέσσερα +MLV χίλια πενήντα πέντε +MLVI χίλια πενήντα έξι +MLVII χίλια πενήντα εφτά +MLVIII χίλια πενήντα οχτώ +MLIX χίλια πενήντα εννιά +MLX χίλια εξήντα +MLXI χίλια εξήντα ένα +MLXII χίλια εξήντα δύο +MLXIII χίλια εξήντα τρία +MLXIV χίλια εξήντα τέσσερα +MLXV χίλια εξήντα πέντε +MLXVI χίλια εξήντα έξι +MLXVII χίλια εξήντα εφτά +MLXVIII χίλια εξήντα οχτώ +MLXIX χίλια εξήντα εννιά +MLXX χίλια εβδομήντα +MLXXI χίλια εβδομήντα ένα +MLXXII χίλια εβδομήντα δύο +MLXXIII χίλια εβδομήντα τρία +MLXXIV χίλια εβδομήντα τέσσερα +MLXXV χίλια εβδομήντα πέντε +MLXXVI χίλια εβδομήντα έξι +MLXXVII χίλια εβδομήντα εφτά +MLXXVIII χίλια εβδομήντα οχτώ +MLXXIX χίλια εβδομήντα εννιά +MLXXX χίλια ογδόντα +MLXXXI χίλια ογδόντα ένα +MLXXXII χίλια ογδόντα δύο +MLXXXIII χίλια ογδόντα τρία +MLXXXIV χίλια ογδόντα τέσσερα +MLXXXV χίλια ογδόντα πέντε +MLXXXVI χίλια ογδόντα έξι +MLXXXVII χίλια ογδόντα εφτά +MLXXXVIII χίλια ογδόντα οχτώ +MLXXXIX χίλια ογδόντα εννιά +MXC χίλια ενενήντα +MXCI χίλια ενενήντα ένα +MXCII χίλια ενενήντα δύο +MXCIII χίλια ενενήντα τρία +MXCIV χίλια ενενήντα τέσσερα +MXCV χίλια ενενήντα πέντε +MXCVI χίλια ενενήντα έξι +MXCVII χίλια ενενήντα εφτά +MXCVIII χίλια ενενήντα οχτώ +MXCIX χίλια ενενήντα εννιά +MC χίλια εκατό +MCI χίλια εκατόν ένα +MCII χίλια εκατόν δύο +MCIII χίλια εκατόν τρία +MCIV χίλια εκατόν τέσσερα +MCV χίλια εκατόν πέντε +MCVI χίλια εκατόν έξι +MCVII χίλια εκατόν εφτά +MCVIII χίλια εκατόν οχτώ +MCIX χίλια εκατόν εννιά +MCX χίλια εκατόν δέκα +MCXI χίλια εκατόν έντεκα +MCXII χίλια εκατόν δώδεκα +MCXIII χίλια εκατόν δεκατρία +MCXIV χίλια εκατόν δεκατέσσερα +MCXV χίλια εκατόν δεκαπέντε +MCXVI χίλια εκατόν δεκαέξι +MCXVII χίλια εκατόν δεκαεφτά +MCXVIII χίλια εκατόν δεκαοχτώ +MCXIX χίλια εκατόν δεκαεννιά +MCXX χίλια εκατόν είκοσι +MCXXI χίλια εκατόν είκοσι ένα +MCXXII χίλια εκατόν είκοσι δύο +MCXXIII χίλια εκατόν είκοσι τρία +MCXXIV χίλια εκατόν είκοσι τέσσερα +MCXXV χίλια εκατόν είκοσι πέντε +MCXXVI χίλια εκατόν είκοσι έξι +MCXXVII χίλια εκατόν είκοσι εφτά +MCXXVIII χίλια εκατόν είκοσι οχτώ +MCXXIX χίλια εκατόν είκοσι εννιά +MCXXX χίλια εκατόν τριάντα +MCXXXI χίλια εκατόν τριάντα ένα +MCXXXII χίλια εκατόν τριάντα δύο +MCXXXIII χίλια εκατόν τριάντα τρία +MCXXXIV χίλια εκατόν τριάντα τέσσερα +MCXXXV χίλια εκατόν τριάντα πέντε +MCXXXVI χίλια εκατόν τριάντα έξι +MCXXXVII χίλια εκατόν τριάντα εφτά +MCXXXVIII χίλια εκατόν τριάντα οχτώ +MCXXXIX χίλια εκατόν τριάντα εννιά +MCXL χίλια εκατόν σαράντα +MCXLI χίλια εκατόν σαράντα ένα +MCXLII χίλια εκατόν σαράντα δύο +MCXLIII χίλια εκατόν σαράντα τρία +MCXLIV χίλια εκατόν σαράντα τέσσερα +MCXLV χίλια εκατόν σαράντα πέντε +MCXLVI χίλια εκατόν σαράντα έξι +MCXLVII χίλια εκατόν σαράντα εφτά +MCXLVIII χίλια εκατόν σαράντα οχτώ +MCXLIX χίλια εκατόν σαράντα εννιά +MCL χίλια εκατόν πενήντα +MCLI χίλια εκατόν πενήντα ένα +MCLII χίλια εκατόν πενήντα δύο +MCLIII χίλια εκατόν πενήντα τρία +MCLIV χίλια εκατόν πενήντα τέσσερα +MCLV χίλια εκατόν πενήντα πέντε +MCLVI χίλια εκατόν πενήντα έξι +MCLVII χίλια εκατόν πενήντα εφτά +MCLVIII χίλια εκατόν πενήντα οχτώ +MCLIX χίλια εκατόν πενήντα εννιά +MCLX χίλια εκατόν εξήντα +MCLXI χίλια εκατόν εξήντα ένα +MCLXII χίλια εκατόν εξήντα δύο +MCLXIII χίλια εκατόν εξήντα τρία +MCLXIV χίλια εκατόν εξήντα τέσσερα +MCLXV χίλια εκατόν εξήντα πέντε +MCLXVI χίλια εκατόν εξήντα έξι +MCLXVII χίλια εκατόν εξήντα εφτά +MCLXVIII χίλια εκατόν εξήντα οχτώ +MCLXIX χίλια εκατόν εξήντα εννιά +MCLXX χίλια εκατόν εβδομήντα +MCLXXI χίλια εκατόν εβδομήντα ένα +MCLXXII χίλια εκατόν εβδομήντα δύο +MCLXXIII χίλια εκατόν εβδομήντα τρία +MCLXXIV χίλια εκατόν εβδομήντα τέσσερα +MCLXXV χίλια εκατόν εβδομήντα πέντε +MCLXXVI χίλια εκατόν εβδομήντα έξι +MCLXXVII χίλια εκατόν εβδομήντα εφτά +MCLXXVIII χίλια εκατόν εβδομήντα οχτώ +MCLXXIX χίλια εκατόν εβδομήντα εννιά +MCLXXX χίλια εκατόν ογδόντα +MCLXXXI χίλια εκατόν ογδόντα ένα +MCLXXXII χίλια εκατόν ογδόντα δύο +MCLXXXIII χίλια εκατόν ογδόντα τρία +MCLXXXIV χίλια εκατόν ογδόντα τέσσερα +MCLXXXV χίλια εκατόν ογδόντα πέντε +MCLXXXVI χίλια εκατόν ογδόντα έξι +MCLXXXVII χίλια εκατόν ογδόντα εφτά +MCLXXXVIII χίλια εκατόν ογδόντα οχτώ +MCLXXXIX χίλια εκατόν ογδόντα εννιά +MCXC χίλια εκατόν ενενήντα +MCXCI χίλια εκατόν ενενήντα ένα +MCXCII χίλια εκατόν ενενήντα δύο +MCXCIII χίλια εκατόν ενενήντα τρία +MCXCIV χίλια εκατόν ενενήντα τέσσερα +MCXCV χίλια εκατόν ενενήντα πέντε +MCXCVI χίλια εκατόν ενενήντα έξι +MCXCVII χίλια εκατόν ενενήντα εφτά +MCXCVIII χίλια εκατόν ενενήντα οχτώ +MCXCIX χίλια εκατόν ενενήντα εννιά +MCC χίλια διακόσια +MCCI χίλια διακόσια ένα +MCCII χίλια διακόσια δύο +MCCIII χίλια διακόσια τρία +MCCIV χίλια διακόσια τέσσερα +MCCV χίλια διακόσια πέντε +MCCVI χίλια διακόσια έξι +MCCVII χίλια διακόσια εφτά +MCCVIII χίλια διακόσια οχτώ +MCCIX χίλια διακόσια εννιά +MCCX χίλια διακόσια δέκα +MCCXI χίλια διακόσια έντεκα +MCCXII χίλια διακόσια δώδεκα +MCCXIII χίλια διακόσια δεκατρία +MCCXIV χίλια διακόσια δεκατέσσερα +MCCXV χίλια διακόσια δεκαπέντε +MCCXVI χίλια διακόσια δεκαέξι +MCCXVII χίλια διακόσια δεκαεφτά +MCCXVIII χίλια διακόσια δεκαοχτώ +MCCXIX χίλια διακόσια δεκαεννιά +MCCXX χίλια διακόσια είκοσι +MCCXXI χίλια διακόσια είκοσι ένα +MCCXXII χίλια διακόσια είκοσι δύο +MCCXXIII χίλια διακόσια είκοσι τρία +MCCXXIV χίλια διακόσια είκοσι τέσσερα +MCCXXV χίλια διακόσια είκοσι πέντε +MCCXXVI χίλια διακόσια είκοσι έξι +MCCXXVII χίλια διακόσια είκοσι εφτά +MCCXXVIII χίλια διακόσια είκοσι οχτώ +MCCXXIX χίλια διακόσια είκοσι εννιά +MCCXXX χίλια διακόσια τριάντα +MCCXXXI χίλια διακόσια τριάντα ένα +MCCXXXII χίλια διακόσια τριάντα δύο +MCCXXXIII χίλια διακόσια τριάντα τρία +MCCXXXIV χίλια διακόσια τριάντα τέσσερα +MCCXXXV χίλια διακόσια τριάντα πέντε +MCCXXXVI χίλια διακόσια τριάντα έξι +MCCXXXVII χίλια διακόσια τριάντα εφτά +MCCXXXVIII χίλια διακόσια τριάντα οχτώ +MCCXXXIX χίλια διακόσια τριάντα εννιά +MCCXL χίλια διακόσια σαράντα +MCCXLI χίλια διακόσια σαράντα ένα +MCCXLII χίλια διακόσια σαράντα δύο +MCCXLIII χίλια διακόσια σαράντα τρία +MCCXLIV χίλια διακόσια σαράντα τέσσερα +MCCXLV χίλια διακόσια σαράντα πέντε +MCCXLVI χίλια διακόσια σαράντα έξι +MCCXLVII χίλια διακόσια σαράντα εφτά +MCCXLVIII χίλια διακόσια σαράντα οχτώ +MCCXLIX χίλια διακόσια σαράντα εννιά +MCCL χίλια διακόσια πενήντα +MCCLI χίλια διακόσια πενήντα ένα +MCCLII χίλια διακόσια πενήντα δύο +MCCLIII χίλια διακόσια πενήντα τρία +MCCLIV χίλια διακόσια πενήντα τέσσερα +MCCLV χίλια διακόσια πενήντα πέντε +MCCLVI χίλια διακόσια πενήντα έξι +MCCLVII χίλια διακόσια πενήντα εφτά +MCCLVIII χίλια διακόσια πενήντα οχτώ +MCCLIX χίλια διακόσια πενήντα εννιά +MCCLX χίλια διακόσια εξήντα +MCCLXI χίλια διακόσια εξήντα ένα +MCCLXII χίλια διακόσια εξήντα δύο +MCCLXIII χίλια διακόσια εξήντα τρία +MCCLXIV χίλια διακόσια εξήντα τέσσερα +MCCLXV χίλια διακόσια εξήντα πέντε +MCCLXVI χίλια διακόσια εξήντα έξι +MCCLXVII χίλια διακόσια εξήντα εφτά +MCCLXVIII χίλια διακόσια εξήντα οχτώ +MCCLXIX χίλια διακόσια εξήντα εννιά +MCCLXX χίλια διακόσια εβδομήντα +MCCLXXI χίλια διακόσια εβδομήντα ένα +MCCLXXII χίλια διακόσια εβδομήντα δύο +MCCLXXIII χίλια διακόσια εβδομήντα τρία +MCCLXXIV χίλια διακόσια εβδομήντα τέσσερα +MCCLXXV χίλια διακόσια εβδομήντα πέντε +MCCLXXVI χίλια διακόσια εβδομήντα έξι +MCCLXXVII χίλια διακόσια εβδομήντα εφτά +MCCLXXVIII χίλια διακόσια εβδομήντα οχτώ +MCCLXXIX χίλια διακόσια εβδομήντα εννιά +MCCLXXX χίλια διακόσια ογδόντα +MCCLXXXI χίλια διακόσια ογδόντα ένα +MCCLXXXII χίλια διακόσια ογδόντα δύο +MCCLXXXIII χίλια διακόσια ογδόντα τρία +MCCLXXXIV χίλια διακόσια ογδόντα τέσσερα +MCCLXXXV χίλια διακόσια ογδόντα πέντε +MCCLXXXVI χίλια διακόσια ογδόντα έξι +MCCLXXXVII χίλια διακόσια ογδόντα εφτά +MCCLXXXVIII χίλια διακόσια ογδόντα οχτώ +MCCLXXXIX χίλια διακόσια ογδόντα εννιά +MCCXC χίλια διακόσια ενενήντα +MCCXCI χίλια διακόσια ενενήντα ένα +MCCXCII χίλια διακόσια ενενήντα δύο +MCCXCIII χίλια διακόσια ενενήντα τρία +MCCXCIV χίλια διακόσια ενενήντα τέσσερα +MCCXCV χίλια διακόσια ενενήντα πέντε +MCCXCVI χίλια διακόσια ενενήντα έξι +MCCXCVII χίλια διακόσια ενενήντα εφτά +MCCXCVIII χίλια διακόσια ενενήντα οχτώ +MCCXCIX χίλια διακόσια ενενήντα εννιά +MCCC χίλια τριακόσια +MCCCI χίλια τριακόσια ένα +MCCCII χίλια τριακόσια δύο +MCCCIII χίλια τριακόσια τρία +MCCCIV χίλια τριακόσια τέσσερα +MCCCV χίλια τριακόσια πέντε +MCCCVI χίλια τριακόσια έξι +MCCCVII χίλια τριακόσια εφτά +MCCCVIII χίλια τριακόσια οχτώ +MCCCIX χίλια τριακόσια εννιά +MCCCX χίλια τριακόσια δέκα +MCCCXI χίλια τριακόσια έντεκα +MCCCXII χίλια τριακόσια δώδεκα +MCCCXIII χίλια τριακόσια δεκατρία +MCCCXIV χίλια τριακόσια δεκατέσσερα +MCCCXV χίλια τριακόσια δεκαπέντε +MCCCXVI χίλια τριακόσια δεκαέξι +MCCCXVII χίλια τριακόσια δεκαεφτά +MCCCXVIII χίλια τριακόσια δεκαοχτώ +MCCCXIX χίλια τριακόσια δεκαεννιά +MCCCXX χίλια τριακόσια είκοσι +MCCCXXI χίλια τριακόσια είκοσι ένα +MCCCXXII χίλια τριακόσια είκοσι δύο +MCCCXXIII χίλια τριακόσια είκοσι τρία +MCCCXXIV χίλια τριακόσια είκοσι τέσσερα +MCCCXXV χίλια τριακόσια είκοσι πέντε +MCCCXXVI χίλια τριακόσια είκοσι έξι +MCCCXXVII χίλια τριακόσια είκοσι εφτά +MCCCXXVIII χίλια τριακόσια είκοσι οχτώ +MCCCXXIX χίλια τριακόσια είκοσι εννιά +MCCCXXX χίλια τριακόσια τριάντα +MCCCXXXI χίλια τριακόσια τριάντα ένα +MCCCXXXII χίλια τριακόσια τριάντα δύο +MCCCXXXIII χίλια τριακόσια τριάντα τρία +MCCCXXXIV χίλια τριακόσια τριάντα τέσσερα +MCCCXXXV χίλια τριακόσια τριάντα πέντε +MCCCXXXVI χίλια τριακόσια τριάντα έξι +MCCCXXXVII χίλια τριακόσια τριάντα εφτά +MCCCXXXVIII χίλια τριακόσια τριάντα οχτώ +MCCCXXXIX χίλια τριακόσια τριάντα εννιά +MCCCXL χίλια τριακόσια σαράντα +MCCCXLI χίλια τριακόσια σαράντα ένα +MCCCXLII χίλια τριακόσια σαράντα δύο +MCCCXLIII χίλια τριακόσια σαράντα τρία +MCCCXLIV χίλια τριακόσια σαράντα τέσσερα +MCCCXLV χίλια τριακόσια σαράντα πέντε +MCCCXLVI χίλια τριακόσια σαράντα έξι +MCCCXLVII χίλια τριακόσια σαράντα εφτά +MCCCXLVIII χίλια τριακόσια σαράντα οχτώ +MCCCXLIX χίλια τριακόσια σαράντα εννιά +MCCCL χίλια τριακόσια πενήντα +MCCCLI χίλια τριακόσια πενήντα ένα +MCCCLII χίλια τριακόσια πενήντα δύο +MCCCLIII χίλια τριακόσια πενήντα τρία +MCCCLIV χίλια τριακόσια πενήντα τέσσερα +MCCCLV χίλια τριακόσια πενήντα πέντε +MCCCLVI χίλια τριακόσια πενήντα έξι +MCCCLVII χίλια τριακόσια πενήντα εφτά +MCCCLVIII χίλια τριακόσια πενήντα οχτώ +MCCCLIX χίλια τριακόσια πενήντα εννιά +MCCCLX χίλια τριακόσια εξήντα +MCCCLXI χίλια τριακόσια εξήντα ένα +MCCCLXII χίλια τριακόσια εξήντα δύο +MCCCLXIII χίλια τριακόσια εξήντα τρία +MCCCLXIV χίλια τριακόσια εξήντα τέσσερα +MCCCLXV χίλια τριακόσια εξήντα πέντε +MCCCLXVI χίλια τριακόσια εξήντα έξι +MCCCLXVII χίλια τριακόσια εξήντα εφτά +MCCCLXVIII χίλια τριακόσια εξήντα οχτώ +MCCCLXIX χίλια τριακόσια εξήντα εννιά +MCCCLXX χίλια τριακόσια εβδομήντα +MCCCLXXI χίλια τριακόσια εβδομήντα ένα +MCCCLXXII χίλια τριακόσια εβδομήντα δύο +MCCCLXXIII χίλια τριακόσια εβδομήντα τρία +MCCCLXXIV χίλια τριακόσια εβδομήντα τέσσερα +MCCCLXXV χίλια τριακόσια εβδομήντα πέντε +MCCCLXXVI χίλια τριακόσια εβδομήντα έξι +MCCCLXXVII χίλια τριακόσια εβδομήντα εφτά +MCCCLXXVIII χίλια τριακόσια εβδομήντα οχτώ +MCCCLXXIX χίλια τριακόσια εβδομήντα εννιά +MCCCLXXX χίλια τριακόσια ογδόντα +MCCCLXXXI χίλια τριακόσια ογδόντα ένα +MCCCLXXXII χίλια τριακόσια ογδόντα δύο +MCCCLXXXIII χίλια τριακόσια ογδόντα τρία +MCCCLXXXIV χίλια τριακόσια ογδόντα τέσσερα +MCCCLXXXV χίλια τριακόσια ογδόντα πέντε +MCCCLXXXVI χίλια τριακόσια ογδόντα έξι +MCCCLXXXVII χίλια τριακόσια ογδόντα εφτά +MCCCLXXXVIII χίλια τριακόσια ογδόντα οχτώ +MCCCLXXXIX χίλια τριακόσια ογδόντα εννιά +MCCCXC χίλια τριακόσια ενενήντα +MCCCXCI χίλια τριακόσια ενενήντα ένα +MCCCXCII χίλια τριακόσια ενενήντα δύο +MCCCXCIII χίλια τριακόσια ενενήντα τρία +MCCCXCIV χίλια τριακόσια ενενήντα τέσσερα +MCCCXCV χίλια τριακόσια ενενήντα πέντε +MCCCXCVI χίλια τριακόσια ενενήντα έξι +MCCCXCVII χίλια τριακόσια ενενήντα εφτά +MCCCXCVIII χίλια τριακόσια ενενήντα οχτώ +MCCCXCIX χίλια τριακόσια ενενήντα εννιά +MCD χίλια τετρακόσια +MCDI χίλια τετρακόσια ένα +MCDII χίλια τετρακόσια δύο +MCDIII χίλια τετρακόσια τρία +MCDIV χίλια τετρακόσια τέσσερα +MCDV χίλια τετρακόσια πέντε +MCDVI χίλια τετρακόσια έξι +MCDVII χίλια τετρακόσια εφτά +MCDVIII χίλια τετρακόσια οχτώ +MCDIX χίλια τετρακόσια εννιά +MCDX χίλια τετρακόσια δέκα +MCDXI χίλια τετρακόσια έντεκα +MCDXII χίλια τετρακόσια δώδεκα +MCDXIII χίλια τετρακόσια δεκατρία +MCDXIV χίλια τετρακόσια δεκατέσσερα +MCDXV χίλια τετρακόσια δεκαπέντε +MCDXVI χίλια τετρακόσια δεκαέξι +MCDXVII χίλια τετρακόσια δεκαεφτά +MCDXVIII χίλια τετρακόσια δεκαοχτώ +MCDXIX χίλια τετρακόσια δεκαεννιά +MCDXX χίλια τετρακόσια είκοσι +MCDXXI χίλια τετρακόσια είκοσι ένα +MCDXXII χίλια τετρακόσια είκοσι δύο +MCDXXIII χίλια τετρακόσια είκοσι τρία +MCDXXIV χίλια τετρακόσια είκοσι τέσσερα +MCDXXV χίλια τετρακόσια είκοσι πέντε +MCDXXVI χίλια τετρακόσια είκοσι έξι +MCDXXVII χίλια τετρακόσια είκοσι εφτά +MCDXXVIII χίλια τετρακόσια είκοσι οχτώ +MCDXXIX χίλια τετρακόσια είκοσι εννιά +MCDXXX χίλια τετρακόσια τριάντα +MCDXXXI χίλια τετρακόσια τριάντα ένα +MCDXXXII χίλια τετρακόσια τριάντα δύο +MCDXXXIII χίλια τετρακόσια τριάντα τρία +MCDXXXIV χίλια τετρακόσια τριάντα τέσσερα +MCDXXXV χίλια τετρακόσια τριάντα πέντε +MCDXXXVI χίλια τετρακόσια τριάντα έξι +MCDXXXVII χίλια τετρακόσια τριάντα εφτά +MCDXXXVIII χίλια τετρακόσια τριάντα οχτώ +MCDXXXIX χίλια τετρακόσια τριάντα εννιά +MCDXL χίλια τετρακόσια σαράντα +MCDXLI χίλια τετρακόσια σαράντα ένα +MCDXLII χίλια τετρακόσια σαράντα δύο +MCDXLIII χίλια τετρακόσια σαράντα τρία +MCDXLIV χίλια τετρακόσια σαράντα τέσσερα +MCDXLV χίλια τετρακόσια σαράντα πέντε +MCDXLVI χίλια τετρακόσια σαράντα έξι +MCDXLVII χίλια τετρακόσια σαράντα εφτά +MCDXLVIII χίλια τετρακόσια σαράντα οχτώ +MCDXLIX χίλια τετρακόσια σαράντα εννιά +MCDL χίλια τετρακόσια πενήντα +MCDLI χίλια τετρακόσια πενήντα ένα +MCDLII χίλια τετρακόσια πενήντα δύο +MCDLIII χίλια τετρακόσια πενήντα τρία +MCDLIV χίλια τετρακόσια πενήντα τέσσερα +MCDLV χίλια τετρακόσια πενήντα πέντε +MCDLVI χίλια τετρακόσια πενήντα έξι +MCDLVII χίλια τετρακόσια πενήντα εφτά +MCDLVIII χίλια τετρακόσια πενήντα οχτώ +MCDLIX χίλια τετρακόσια πενήντα εννιά +MCDLX χίλια τετρακόσια εξήντα +MCDLXI χίλια τετρακόσια εξήντα ένα +MCDLXII χίλια τετρακόσια εξήντα δύο +MCDLXIII χίλια τετρακόσια εξήντα τρία +MCDLXIV χίλια τετρακόσια εξήντα τέσσερα +MCDLXV χίλια τετρακόσια εξήντα πέντε +MCDLXVI χίλια τετρακόσια εξήντα έξι +MCDLXVII χίλια τετρακόσια εξήντα εφτά +MCDLXVIII χίλια τετρακόσια εξήντα οχτώ +MCDLXIX χίλια τετρακόσια εξήντα εννιά +MCDLXX χίλια τετρακόσια εβδομήντα +MCDLXXI χίλια τετρακόσια εβδομήντα ένα +MCDLXXII χίλια τετρακόσια εβδομήντα δύο +MCDLXXIII χίλια τετρακόσια εβδομήντα τρία +MCDLXXIV χίλια τετρακόσια εβδομήντα τέσσερα +MCDLXXV χίλια τετρακόσια εβδομήντα πέντε +MCDLXXVI χίλια τετρακόσια εβδομήντα έξι +MCDLXXVII χίλια τετρακόσια εβδομήντα εφτά +MCDLXXVIII χίλια τετρακόσια εβδομήντα οχτώ +MCDLXXIX χίλια τετρακόσια εβδομήντα εννιά +MCDLXXX χίλια τετρακόσια ογδόντα +MCDLXXXI χίλια τετρακόσια ογδόντα ένα +MCDLXXXII χίλια τετρακόσια ογδόντα δύο +MCDLXXXIII χίλια τετρακόσια ογδόντα τρία +MCDLXXXIV χίλια τετρακόσια ογδόντα τέσσερα +MCDLXXXV χίλια τετρακόσια ογδόντα πέντε +MCDLXXXVI χίλια τετρακόσια ογδόντα έξι +MCDLXXXVII χίλια τετρακόσια ογδόντα εφτά +MCDLXXXVIII χίλια τετρακόσια ογδόντα οχτώ +MCDLXXXIX χίλια τετρακόσια ογδόντα εννιά +MCDXC χίλια τετρακόσια ενενήντα +MCDXCI χίλια τετρακόσια ενενήντα ένα +MCDXCII χίλια τετρακόσια ενενήντα δύο +MCDXCIII χίλια τετρακόσια ενενήντα τρία +MCDXCIV χίλια τετρακόσια ενενήντα τέσσερα +MCDXCV χίλια τετρακόσια ενενήντα πέντε +MCDXCVI χίλια τετρακόσια ενενήντα έξι +MCDXCVII χίλια τετρακόσια ενενήντα εφτά +MCDXCVIII χίλια τετρακόσια ενενήντα οχτώ +MCDXCIX χίλια τετρακόσια ενενήντα εννιά +MD χίλια πεντακόσια +MDI χίλια πεντακόσια ένα +MDII χίλια πεντακόσια δύο +MDIII χίλια πεντακόσια τρία +MDIV χίλια πεντακόσια τέσσερα +MDV χίλια πεντακόσια πέντε +MDVI χίλια πεντακόσια έξι +MDVII χίλια πεντακόσια εφτά +MDVIII χίλια πεντακόσια οχτώ +MDIX χίλια πεντακόσια εννιά +MDX χίλια πεντακόσια δέκα +MDXI χίλια πεντακόσια έντεκα +MDXII χίλια πεντακόσια δώδεκα +MDXIII χίλια πεντακόσια δεκατρία +MDXIV χίλια πεντακόσια δεκατέσσερα +MDXV χίλια πεντακόσια δεκαπέντε +MDXVI χίλια πεντακόσια δεκαέξι +MDXVII χίλια πεντακόσια δεκαεφτά +MDXVIII χίλια πεντακόσια δεκαοχτώ +MDXIX χίλια πεντακόσια δεκαεννιά +MDXX χίλια πεντακόσια είκοσι +MDXXI χίλια πεντακόσια είκοσι ένα +MDXXII χίλια πεντακόσια είκοσι δύο +MDXXIII χίλια πεντακόσια είκοσι τρία +MDXXIV χίλια πεντακόσια είκοσι τέσσερα +MDXXV χίλια πεντακόσια είκοσι πέντε +MDXXVI χίλια πεντακόσια είκοσι έξι +MDXXVII χίλια πεντακόσια είκοσι εφτά +MDXXVIII χίλια πεντακόσια είκοσι οχτώ +MDXXIX χίλια πεντακόσια είκοσι εννιά +MDXXX χίλια πεντακόσια τριάντα +MDXXXI χίλια πεντακόσια τριάντα ένα +MDXXXII χίλια πεντακόσια τριάντα δύο +MDXXXIII χίλια πεντακόσια τριάντα τρία +MDXXXIV χίλια πεντακόσια τριάντα τέσσερα +MDXXXV χίλια πεντακόσια τριάντα πέντε +MDXXXVI χίλια πεντακόσια τριάντα έξι +MDXXXVII χίλια πεντακόσια τριάντα εφτά +MDXXXVIII χίλια πεντακόσια τριάντα οχτώ +MDXXXIX χίλια πεντακόσια τριάντα εννιά +MDXL χίλια πεντακόσια σαράντα +MDXLI χίλια πεντακόσια σαράντα ένα +MDXLII χίλια πεντακόσια σαράντα δύο +MDXLIII χίλια πεντακόσια σαράντα τρία +MDXLIV χίλια πεντακόσια σαράντα τέσσερα +MDXLV χίλια πεντακόσια σαράντα πέντε +MDXLVI χίλια πεντακόσια σαράντα έξι +MDXLVII χίλια πεντακόσια σαράντα εφτά +MDXLVIII χίλια πεντακόσια σαράντα οχτώ +MDXLIX χίλια πεντακόσια σαράντα εννιά +MDL χίλια πεντακόσια πενήντα +MDLI χίλια πεντακόσια πενήντα ένα +MDLII χίλια πεντακόσια πενήντα δύο +MDLIII χίλια πεντακόσια πενήντα τρία +MDLIV χίλια πεντακόσια πενήντα τέσσερα +MDLV χίλια πεντακόσια πενήντα πέντε +MDLVI χίλια πεντακόσια πενήντα έξι +MDLVII χίλια πεντακόσια πενήντα εφτά +MDLVIII χίλια πεντακόσια πενήντα οχτώ +MDLIX χίλια πεντακόσια πενήντα εννιά +MDLX χίλια πεντακόσια εξήντα +MDLXI χίλια πεντακόσια εξήντα ένα +MDLXII χίλια πεντακόσια εξήντα δύο +MDLXIII χίλια πεντακόσια εξήντα τρία +MDLXIV χίλια πεντακόσια εξήντα τέσσερα +MDLXV χίλια πεντακόσια εξήντα πέντε +MDLXVI χίλια πεντακόσια εξήντα έξι +MDLXVII χίλια πεντακόσια εξήντα εφτά +MDLXVIII χίλια πεντακόσια εξήντα οχτώ +MDLXIX χίλια πεντακόσια εξήντα εννιά +MDLXX χίλια πεντακόσια εβδομήντα +MDLXXI χίλια πεντακόσια εβδομήντα ένα +MDLXXII χίλια πεντακόσια εβδομήντα δύο +MDLXXIII χίλια πεντακόσια εβδομήντα τρία +MDLXXIV χίλια πεντακόσια εβδομήντα τέσσερα +MDLXXV χίλια πεντακόσια εβδομήντα πέντε +MDLXXVI χίλια πεντακόσια εβδομήντα έξι +MDLXXVII χίλια πεντακόσια εβδομήντα εφτά +MDLXXVIII χίλια πεντακόσια εβδομήντα οχτώ +MDLXXIX χίλια πεντακόσια εβδομήντα εννιά +MDLXXX χίλια πεντακόσια ογδόντα +MDLXXXI χίλια πεντακόσια ογδόντα ένα +MDLXXXII χίλια πεντακόσια ογδόντα δύο +MDLXXXIII χίλια πεντακόσια ογδόντα τρία +MDLXXXIV χίλια πεντακόσια ογδόντα τέσσερα +MDLXXXV χίλια πεντακόσια ογδόντα πέντε +MDLXXXVI χίλια πεντακόσια ογδόντα έξι +MDLXXXVII χίλια πεντακόσια ογδόντα εφτά +MDLXXXVIII χίλια πεντακόσια ογδόντα οχτώ +MDLXXXIX χίλια πεντακόσια ογδόντα εννιά +MDXC χίλια πεντακόσια ενενήντα +MDXCI χίλια πεντακόσια ενενήντα ένα +MDXCII χίλια πεντακόσια ενενήντα δύο +MDXCIII χίλια πεντακόσια ενενήντα τρία +MDXCIV χίλια πεντακόσια ενενήντα τέσσερα +MDXCV χίλια πεντακόσια ενενήντα πέντε +MDXCVI χίλια πεντακόσια ενενήντα έξι +MDXCVII χίλια πεντακόσια ενενήντα εφτά +MDXCVIII χίλια πεντακόσια ενενήντα οχτώ +MDXCIX χίλια πεντακόσια ενενήντα εννιά +MDC χίλια εξακόσια +MDCI χίλια εξακόσια ένα +MDCII χίλια εξακόσια δύο +MDCIII χίλια εξακόσια τρία +MDCIV χίλια εξακόσια τέσσερα +MDCV χίλια εξακόσια πέντε +MDCVI χίλια εξακόσια έξι +MDCVII χίλια εξακόσια εφτά +MDCVIII χίλια εξακόσια οχτώ +MDCIX χίλια εξακόσια εννιά +MDCX χίλια εξακόσια δέκα +MDCXI χίλια εξακόσια έντεκα +MDCXII χίλια εξακόσια δώδεκα +MDCXIII χίλια εξακόσια δεκατρία +MDCXIV χίλια εξακόσια δεκατέσσερα +MDCXV χίλια εξακόσια δεκαπέντε +MDCXVI χίλια εξακόσια δεκαέξι +MDCXVII χίλια εξακόσια δεκαεφτά +MDCXVIII χίλια εξακόσια δεκαοχτώ +MDCXIX χίλια εξακόσια δεκαεννιά +MDCXX χίλια εξακόσια είκοσι +MDCXXI χίλια εξακόσια είκοσι ένα +MDCXXII χίλια εξακόσια είκοσι δύο +MDCXXIII χίλια εξακόσια είκοσι τρία +MDCXXIV χίλια εξακόσια είκοσι τέσσερα +MDCXXV χίλια εξακόσια είκοσι πέντε +MDCXXVI χίλια εξακόσια είκοσι έξι +MDCXXVII χίλια εξακόσια είκοσι εφτά +MDCXXVIII χίλια εξακόσια είκοσι οχτώ +MDCXXIX χίλια εξακόσια είκοσι εννιά +MDCXXX χίλια εξακόσια τριάντα +MDCXXXI χίλια εξακόσια τριάντα ένα +MDCXXXII χίλια εξακόσια τριάντα δύο +MDCXXXIII χίλια εξακόσια τριάντα τρία +MDCXXXIV χίλια εξακόσια τριάντα τέσσερα +MDCXXXV χίλια εξακόσια τριάντα πέντε +MDCXXXVI χίλια εξακόσια τριάντα έξι +MDCXXXVII χίλια εξακόσια τριάντα εφτά +MDCXXXVIII χίλια εξακόσια τριάντα οχτώ +MDCXXXIX χίλια εξακόσια τριάντα εννιά +MDCXL χίλια εξακόσια σαράντα +MDCXLI χίλια εξακόσια σαράντα ένα +MDCXLII χίλια εξακόσια σαράντα δύο +MDCXLIII χίλια εξακόσια σαράντα τρία +MDCXLIV χίλια εξακόσια σαράντα τέσσερα +MDCXLV χίλια εξακόσια σαράντα πέντε +MDCXLVI χίλια εξακόσια σαράντα έξι +MDCXLVII χίλια εξακόσια σαράντα εφτά +MDCXLVIII χίλια εξακόσια σαράντα οχτώ +MDCXLIX χίλια εξακόσια σαράντα εννιά +MDCL χίλια εξακόσια πενήντα +MDCLI χίλια εξακόσια πενήντα ένα +MDCLII χίλια εξακόσια πενήντα δύο +MDCLIII χίλια εξακόσια πενήντα τρία +MDCLIV χίλια εξακόσια πενήντα τέσσερα +MDCLV χίλια εξακόσια πενήντα πέντε +MDCLVI χίλια εξακόσια πενήντα έξι +MDCLVII χίλια εξακόσια πενήντα εφτά +MDCLVIII χίλια εξακόσια πενήντα οχτώ +MDCLIX χίλια εξακόσια πενήντα εννιά +MDCLX χίλια εξακόσια εξήντα +MDCLXI χίλια εξακόσια εξήντα ένα +MDCLXII χίλια εξακόσια εξήντα δύο +MDCLXIII χίλια εξακόσια εξήντα τρία +MDCLXIV χίλια εξακόσια εξήντα τέσσερα +MDCLXV χίλια εξακόσια εξήντα πέντε +MDCLXVI χίλια εξακόσια εξήντα έξι +MDCLXVII χίλια εξακόσια εξήντα εφτά +MDCLXVIII χίλια εξακόσια εξήντα οχτώ +MDCLXIX χίλια εξακόσια εξήντα εννιά +MDCLXX χίλια εξακόσια εβδομήντα +MDCLXXI χίλια εξακόσια εβδομήντα ένα +MDCLXXII χίλια εξακόσια εβδομήντα δύο +MDCLXXIII χίλια εξακόσια εβδομήντα τρία +MDCLXXIV χίλια εξακόσια εβδομήντα τέσσερα +MDCLXXV χίλια εξακόσια εβδομήντα πέντε +MDCLXXVI χίλια εξακόσια εβδομήντα έξι +MDCLXXVII χίλια εξακόσια εβδομήντα εφτά +MDCLXXVIII χίλια εξακόσια εβδομήντα οχτώ +MDCLXXIX χίλια εξακόσια εβδομήντα εννιά +MDCLXXX χίλια εξακόσια ογδόντα +MDCLXXXI χίλια εξακόσια ογδόντα ένα +MDCLXXXII χίλια εξακόσια ογδόντα δύο +MDCLXXXIII χίλια εξακόσια ογδόντα τρία +MDCLXXXIV χίλια εξακόσια ογδόντα τέσσερα +MDCLXXXV χίλια εξακόσια ογδόντα πέντε +MDCLXXXVI χίλια εξακόσια ογδόντα έξι +MDCLXXXVII χίλια εξακόσια ογδόντα εφτά +MDCLXXXVIII χίλια εξακόσια ογδόντα οχτώ +MDCLXXXIX χίλια εξακόσια ογδόντα εννιά +MDCXC χίλια εξακόσια ενενήντα +MDCXCI χίλια εξακόσια ενενήντα ένα +MDCXCII χίλια εξακόσια ενενήντα δύο +MDCXCIII χίλια εξακόσια ενενήντα τρία +MDCXCIV χίλια εξακόσια ενενήντα τέσσερα +MDCXCV χίλια εξακόσια ενενήντα πέντε +MDCXCVI χίλια εξακόσια ενενήντα έξι +MDCXCVII χίλια εξακόσια ενενήντα εφτά +MDCXCVIII χίλια εξακόσια ενενήντα οχτώ +MDCXCIX χίλια εξακόσια ενενήντα εννιά +MDCC χίλια εφτακόσια +MDCCI χίλια εφτακόσια ένα +MDCCII χίλια εφτακόσια δύο +MDCCIII χίλια εφτακόσια τρία +MDCCIV χίλια εφτακόσια τέσσερα +MDCCV χίλια εφτακόσια πέντε +MDCCVI χίλια εφτακόσια έξι +MDCCVII χίλια εφτακόσια εφτά +MDCCVIII χίλια εφτακόσια οχτώ +MDCCIX χίλια εφτακόσια εννιά +MDCCX χίλια εφτακόσια δέκα +MDCCXI χίλια εφτακόσια έντεκα +MDCCXII χίλια εφτακόσια δώδεκα +MDCCXIII χίλια εφτακόσια δεκατρία +MDCCXIV χίλια εφτακόσια δεκατέσσερα +MDCCXV χίλια εφτακόσια δεκαπέντε +MDCCXVI χίλια εφτακόσια δεκαέξι +MDCCXVII χίλια εφτακόσια δεκαεφτά +MDCCXVIII χίλια εφτακόσια δεκαοχτώ +MDCCXIX χίλια εφτακόσια δεκαεννιά +MDCCXX χίλια εφτακόσια είκοσι +MDCCXXI χίλια εφτακόσια είκοσι ένα +MDCCXXII χίλια εφτακόσια είκοσι δύο +MDCCXXIII χίλια εφτακόσια είκοσι τρία +MDCCXXIV χίλια εφτακόσια είκοσι τέσσερα +MDCCXXV χίλια εφτακόσια είκοσι πέντε +MDCCXXVI χίλια εφτακόσια είκοσι έξι +MDCCXXVII χίλια εφτακόσια είκοσι εφτά +MDCCXXVIII χίλια εφτακόσια είκοσι οχτώ +MDCCXXIX χίλια εφτακόσια είκοσι εννιά +MDCCXXX χίλια εφτακόσια τριάντα +MDCCXXXI χίλια εφτακόσια τριάντα ένα +MDCCXXXII χίλια εφτακόσια τριάντα δύο +MDCCXXXIII χίλια εφτακόσια τριάντα τρία +MDCCXXXIV χίλια εφτακόσια τριάντα τέσσερα +MDCCXXXV χίλια εφτακόσια τριάντα πέντε +MDCCXXXVI χίλια εφτακόσια τριάντα έξι +MDCCXXXVII χίλια εφτακόσια τριάντα εφτά +MDCCXXXVIII χίλια εφτακόσια τριάντα οχτώ +MDCCXXXIX χίλια εφτακόσια τριάντα εννιά +MDCCXL χίλια εφτακόσια σαράντα +MDCCXLI χίλια εφτακόσια σαράντα ένα +MDCCXLII χίλια εφτακόσια σαράντα δύο +MDCCXLIII χίλια εφτακόσια σαράντα τρία +MDCCXLIV χίλια εφτακόσια σαράντα τέσσερα +MDCCXLV χίλια εφτακόσια σαράντα πέντε +MDCCXLVI χίλια εφτακόσια σαράντα έξι +MDCCXLVII χίλια εφτακόσια σαράντα εφτά +MDCCXLVIII χίλια εφτακόσια σαράντα οχτώ +MDCCXLIX χίλια εφτακόσια σαράντα εννιά +MDCCL χίλια εφτακόσια πενήντα +MDCCLI χίλια εφτακόσια πενήντα ένα +MDCCLII χίλια εφτακόσια πενήντα δύο +MDCCLIII χίλια εφτακόσια πενήντα τρία +MDCCLIV χίλια εφτακόσια πενήντα τέσσερα +MDCCLV χίλια εφτακόσια πενήντα πέντε +MDCCLVI χίλια εφτακόσια πενήντα έξι +MDCCLVII χίλια εφτακόσια πενήντα εφτά +MDCCLVIII χίλια εφτακόσια πενήντα οχτώ +MDCCLIX χίλια εφτακόσια πενήντα εννιά +MDCCLX χίλια εφτακόσια εξήντα +MDCCLXI χίλια εφτακόσια εξήντα ένα +MDCCLXII χίλια εφτακόσια εξήντα δύο +MDCCLXIII χίλια εφτακόσια εξήντα τρία +MDCCLXIV χίλια εφτακόσια εξήντα τέσσερα +MDCCLXV χίλια εφτακόσια εξήντα πέντε +MDCCLXVI χίλια εφτακόσια εξήντα έξι +MDCCLXVII χίλια εφτακόσια εξήντα εφτά +MDCCLXVIII χίλια εφτακόσια εξήντα οχτώ +MDCCLXIX χίλια εφτακόσια εξήντα εννιά +MDCCLXX χίλια εφτακόσια εβδομήντα +MDCCLXXI χίλια εφτακόσια εβδομήντα ένα +MDCCLXXII χίλια εφτακόσια εβδομήντα δύο +MDCCLXXIII χίλια εφτακόσια εβδομήντα τρία +MDCCLXXIV χίλια εφτακόσια εβδομήντα τέσσερα +MDCCLXXV χίλια εφτακόσια εβδομήντα πέντε +MDCCLXXVI χίλια εφτακόσια εβδομήντα έξι +MDCCLXXVII χίλια εφτακόσια εβδομήντα εφτά +MDCCLXXVIII χίλια εφτακόσια εβδομήντα οχτώ +MDCCLXXIX χίλια εφτακόσια εβδομήντα εννιά +MDCCLXXX χίλια εφτακόσια ογδόντα +MDCCLXXXI χίλια εφτακόσια ογδόντα ένα +MDCCLXXXII χίλια εφτακόσια ογδόντα δύο +MDCCLXXXIII χίλια εφτακόσια ογδόντα τρία +MDCCLXXXIV χίλια εφτακόσια ογδόντα τέσσερα +MDCCLXXXV χίλια εφτακόσια ογδόντα πέντε +MDCCLXXXVI χίλια εφτακόσια ογδόντα έξι +MDCCLXXXVII χίλια εφτακόσια ογδόντα εφτά +MDCCLXXXVIII χίλια εφτακόσια ογδόντα οχτώ +MDCCLXXXIX χίλια εφτακόσια ογδόντα εννιά +MDCCXC χίλια εφτακόσια ενενήντα +MDCCXCI χίλια εφτακόσια ενενήντα ένα +MDCCXCII χίλια εφτακόσια ενενήντα δύο +MDCCXCIII χίλια εφτακόσια ενενήντα τρία +MDCCXCIV χίλια εφτακόσια ενενήντα τέσσερα +MDCCXCV χίλια εφτακόσια ενενήντα πέντε +MDCCXCVI χίλια εφτακόσια ενενήντα έξι +MDCCXCVII χίλια εφτακόσια ενενήντα εφτά +MDCCXCVIII χίλια εφτακόσια ενενήντα οχτώ +MDCCXCIX χίλια εφτακόσια ενενήντα εννιά +MDCCC χίλια οχτακόσια +MDCCCI χίλια οχτακόσια ένα +MDCCCII χίλια οχτακόσια δύο +MDCCCIII χίλια οχτακόσια τρία +MDCCCIV χίλια οχτακόσια τέσσερα +MDCCCV χίλια οχτακόσια πέντε +MDCCCVI χίλια οχτακόσια έξι +MDCCCVII χίλια οχτακόσια εφτά +MDCCCVIII χίλια οχτακόσια οχτώ +MDCCCIX χίλια οχτακόσια εννιά +MDCCCX χίλια οχτακόσια δέκα +MDCCCXI χίλια οχτακόσια έντεκα +MDCCCXII χίλια οχτακόσια δώδεκα +MDCCCXIII χίλια οχτακόσια δεκατρία +MDCCCXIV χίλια οχτακόσια δεκατέσσερα +MDCCCXV χίλια οχτακόσια δεκαπέντε +MDCCCXVI χίλια οχτακόσια δεκαέξι +MDCCCXVII χίλια οχτακόσια δεκαεφτά +MDCCCXVIII χίλια οχτακόσια δεκαοχτώ +MDCCCXIX χίλια οχτακόσια δεκαεννιά +MDCCCXX χίλια οχτακόσια είκοσι +MDCCCXXI χίλια οχτακόσια είκοσι ένα +MDCCCXXII χίλια οχτακόσια είκοσι δύο +MDCCCXXIII χίλια οχτακόσια είκοσι τρία +MDCCCXXIV χίλια οχτακόσια είκοσι τέσσερα +MDCCCXXV χίλια οχτακόσια είκοσι πέντε +MDCCCXXVI χίλια οχτακόσια είκοσι έξι +MDCCCXXVII χίλια οχτακόσια είκοσι εφτά +MDCCCXXVIII χίλια οχτακόσια είκοσι οχτώ +MDCCCXXIX χίλια οχτακόσια είκοσι εννιά +MDCCCXXX χίλια οχτακόσια τριάντα +MDCCCXXXI χίλια οχτακόσια τριάντα ένα +MDCCCXXXII χίλια οχτακόσια τριάντα δύο +MDCCCXXXIII χίλια οχτακόσια τριάντα τρία +MDCCCXXXIV χίλια οχτακόσια τριάντα τέσσερα +MDCCCXXXV χίλια οχτακόσια τριάντα πέντε +MDCCCXXXVI χίλια οχτακόσια τριάντα έξι +MDCCCXXXVII χίλια οχτακόσια τριάντα εφτά +MDCCCXXXVIII χίλια οχτακόσια τριάντα οχτώ +MDCCCXXXIX χίλια οχτακόσια τριάντα εννιά +MDCCCXL χίλια οχτακόσια σαράντα +MDCCCXLI χίλια οχτακόσια σαράντα ένα +MDCCCXLII χίλια οχτακόσια σαράντα δύο +MDCCCXLIII χίλια οχτακόσια σαράντα τρία +MDCCCXLIV χίλια οχτακόσια σαράντα τέσσερα +MDCCCXLV χίλια οχτακόσια σαράντα πέντε +MDCCCXLVI χίλια οχτακόσια σαράντα έξι +MDCCCXLVII χίλια οχτακόσια σαράντα εφτά +MDCCCXLVIII χίλια οχτακόσια σαράντα οχτώ +MDCCCXLIX χίλια οχτακόσια σαράντα εννιά +MDCCCL χίλια οχτακόσια πενήντα +MDCCCLI χίλια οχτακόσια πενήντα ένα +MDCCCLII χίλια οχτακόσια πενήντα δύο +MDCCCLIII χίλια οχτακόσια πενήντα τρία +MDCCCLIV χίλια οχτακόσια πενήντα τέσσερα +MDCCCLV χίλια οχτακόσια πενήντα πέντε +MDCCCLVI χίλια οχτακόσια πενήντα έξι +MDCCCLVII χίλια οχτακόσια πενήντα εφτά +MDCCCLVIII χίλια οχτακόσια πενήντα οχτώ +MDCCCLIX χίλια οχτακόσια πενήντα εννιά +MDCCCLX χίλια οχτακόσια εξήντα +MDCCCLXI χίλια οχτακόσια εξήντα ένα +MDCCCLXII χίλια οχτακόσια εξήντα δύο +MDCCCLXIII χίλια οχτακόσια εξήντα τρία +MDCCCLXIV χίλια οχτακόσια εξήντα τέσσερα +MDCCCLXV χίλια οχτακόσια εξήντα πέντε +MDCCCLXVI χίλια οχτακόσια εξήντα έξι +MDCCCLXVII χίλια οχτακόσια εξήντα εφτά +MDCCCLXVIII χίλια οχτακόσια εξήντα οχτώ +MDCCCLXIX χίλια οχτακόσια εξήντα εννιά +MDCCCLXX χίλια οχτακόσια εβδομήντα +MDCCCLXXI χίλια οχτακόσια εβδομήντα ένα +MDCCCLXXII χίλια οχτακόσια εβδομήντα δύο +MDCCCLXXIII χίλια οχτακόσια εβδομήντα τρία +MDCCCLXXIV χίλια οχτακόσια εβδομήντα τέσσερα +MDCCCLXXV χίλια οχτακόσια εβδομήντα πέντε +MDCCCLXXVI χίλια οχτακόσια εβδομήντα έξι +MDCCCLXXVII χίλια οχτακόσια εβδομήντα εφτά +MDCCCLXXVIII χίλια οχτακόσια εβδομήντα οχτώ +MDCCCLXXIX χίλια οχτακόσια εβδομήντα εννιά +MDCCCLXXX χίλια οχτακόσια ογδόντα +MDCCCLXXXI χίλια οχτακόσια ογδόντα ένα +MDCCCLXXXII χίλια οχτακόσια ογδόντα δύο +MDCCCLXXXIII χίλια οχτακόσια ογδόντα τρία +MDCCCLXXXIV χίλια οχτακόσια ογδόντα τέσσερα +MDCCCLXXXV χίλια οχτακόσια ογδόντα πέντε +MDCCCLXXXVI χίλια οχτακόσια ογδόντα έξι +MDCCCLXXXVII χίλια οχτακόσια ογδόντα εφτά +MDCCCLXXXVIII χίλια οχτακόσια ογδόντα οχτώ +MDCCCLXXXIX χίλια οχτακόσια ογδόντα εννιά +MDCCCXC χίλια οχτακόσια ενενήντα +MDCCCXCI χίλια οχτακόσια ενενήντα ένα +MDCCCXCII χίλια οχτακόσια ενενήντα δύο +MDCCCXCIII χίλια οχτακόσια ενενήντα τρία +MDCCCXCIV χίλια οχτακόσια ενενήντα τέσσερα +MDCCCXCV χίλια οχτακόσια ενενήντα πέντε +MDCCCXCVI χίλια οχτακόσια ενενήντα έξι +MDCCCXCVII χίλια οχτακόσια ενενήντα εφτά +MDCCCXCVIII χίλια οχτακόσια ενενήντα οχτώ +MDCCCXCIX χίλια οχτακόσια ενενήντα εννιά +MCM χίλια εννιακόσια +MCMI χίλια εννιακόσια ένα +MCMII χίλια εννιακόσια δύο +MCMIII χίλια εννιακόσια τρία +MCMIV χίλια εννιακόσια τέσσερα +MCMV χίλια εννιακόσια πέντε +MCMVI χίλια εννιακόσια έξι +MCMVII χίλια εννιακόσια εφτά +MCMVIII χίλια εννιακόσια οχτώ +MCMIX χίλια εννιακόσια εννιά +MCMX χίλια εννιακόσια δέκα +MCMXI χίλια εννιακόσια έντεκα +MCMXII χίλια εννιακόσια δώδεκα +MCMXIII χίλια εννιακόσια δεκατρία +MCMXIV χίλια εννιακόσια δεκατέσσερα +MCMXV χίλια εννιακόσια δεκαπέντε +MCMXVI χίλια εννιακόσια δεκαέξι +MCMXVII χίλια εννιακόσια δεκαεφτά +MCMXVIII χίλια εννιακόσια δεκαοχτώ +MCMXIX χίλια εννιακόσια δεκαεννιά +MCMXX χίλια εννιακόσια είκοσι +MCMXXI χίλια εννιακόσια είκοσι ένα +MCMXXII χίλια εννιακόσια είκοσι δύο +MCMXXIII χίλια εννιακόσια είκοσι τρία +MCMXXIV χίλια εννιακόσια είκοσι τέσσερα +MCMXXV χίλια εννιακόσια είκοσι πέντε +MCMXXVI χίλια εννιακόσια είκοσι έξι +MCMXXVII χίλια εννιακόσια είκοσι εφτά +MCMXXVIII χίλια εννιακόσια είκοσι οχτώ +MCMXXIX χίλια εννιακόσια είκοσι εννιά +MCMXXX χίλια εννιακόσια τριάντα +MCMXXXI χίλια εννιακόσια τριάντα ένα +MCMXXXII χίλια εννιακόσια τριάντα δύο +MCMXXXIII χίλια εννιακόσια τριάντα τρία +MCMXXXIV χίλια εννιακόσια τριάντα τέσσερα +MCMXXXV χίλια εννιακόσια τριάντα πέντε +MCMXXXVI χίλια εννιακόσια τριάντα έξι +MCMXXXVII χίλια εννιακόσια τριάντα εφτά +MCMXXXVIII χίλια εννιακόσια τριάντα οχτώ +MCMXXXIX χίλια εννιακόσια τριάντα εννιά +MCMXL χίλια εννιακόσια σαράντα +MCMXLI χίλια εννιακόσια σαράντα ένα +MCMXLII χίλια εννιακόσια σαράντα δύο +MCMXLIII χίλια εννιακόσια σαράντα τρία +MCMXLIV χίλια εννιακόσια σαράντα τέσσερα +MCMXLV χίλια εννιακόσια σαράντα πέντε +MCMXLVI χίλια εννιακόσια σαράντα έξι +MCMXLVII χίλια εννιακόσια σαράντα εφτά +MCMXLVIII χίλια εννιακόσια σαράντα οχτώ +MCMXLIX χίλια εννιακόσια σαράντα εννιά +MCML χίλια εννιακόσια πενήντα +MCMLI χίλια εννιακόσια πενήντα ένα +MCMLII χίλια εννιακόσια πενήντα δύο +MCMLIII χίλια εννιακόσια πενήντα τρία +MCMLIV χίλια εννιακόσια πενήντα τέσσερα +MCMLV χίλια εννιακόσια πενήντα πέντε +MCMLVI χίλια εννιακόσια πενήντα έξι +MCMLVII χίλια εννιακόσια πενήντα εφτά +MCMLVIII χίλια εννιακόσια πενήντα οχτώ +MCMLIX χίλια εννιακόσια πενήντα εννιά +MCMLX χίλια εννιακόσια εξήντα +MCMLXI χίλια εννιακόσια εξήντα ένα +MCMLXII χίλια εννιακόσια εξήντα δύο +MCMLXIII χίλια εννιακόσια εξήντα τρία +MCMLXIV χίλια εννιακόσια εξήντα τέσσερα +MCMLXV χίλια εννιακόσια εξήντα πέντε +MCMLXVI χίλια εννιακόσια εξήντα έξι +MCMLXVII χίλια εννιακόσια εξήντα εφτά +MCMLXVIII χίλια εννιακόσια εξήντα οχτώ +MCMLXIX χίλια εννιακόσια εξήντα εννιά +MCMLXX χίλια εννιακόσια εβδομήντα +MCMLXXI χίλια εννιακόσια εβδομήντα ένα +MCMLXXII χίλια εννιακόσια εβδομήντα δύο +MCMLXXIII χίλια εννιακόσια εβδομήντα τρία +MCMLXXIV χίλια εννιακόσια εβδομήντα τέσσερα +MCMLXXV χίλια εννιακόσια εβδομήντα πέντε +MCMLXXVI χίλια εννιακόσια εβδομήντα έξι +MCMLXXVII χίλια εννιακόσια εβδομήντα εφτά +MCMLXXVIII χίλια εννιακόσια εβδομήντα οχτώ +MCMLXXIX χίλια εννιακόσια εβδομήντα εννιά +MCMLXXX χίλια εννιακόσια ογδόντα +MCMLXXXI χίλια εννιακόσια ογδόντα ένα +MCMLXXXII χίλια εννιακόσια ογδόντα δύο +MCMLXXXIII χίλια εννιακόσια ογδόντα τρία +MCMLXXXIV χίλια εννιακόσια ογδόντα τέσσερα +MCMLXXXV χίλια εννιακόσια ογδόντα πέντε +MCMLXXXVI χίλια εννιακόσια ογδόντα έξι +MCMLXXXVII χίλια εννιακόσια ογδόντα εφτά +MCMLXXXVIII χίλια εννιακόσια ογδόντα οχτώ +MCMLXXXIX χίλια εννιακόσια ογδόντα εννιά +MCMXC χίλια εννιακόσια ενενήντα +MCMXCI χίλια εννιακόσια ενενήντα ένα +MCMXCII χίλια εννιακόσια ενενήντα δύο +MCMXCIII χίλια εννιακόσια ενενήντα τρία +MCMXCIV χίλια εννιακόσια ενενήντα τέσσερα +MCMXCV χίλια εννιακόσια ενενήντα πέντε +MCMXCVI χίλια εννιακόσια ενενήντα έξι +MCMXCVII χίλια εννιακόσια ενενήντα εφτά +MCMXCVIII χίλια εννιακόσια ενενήντα οχτώ +MCMXCIX χίλια εννιακόσια ενενήντα εννιά +MM δύο χιλιάδες +MMI δύο χιλιάδες ένα +MMII δύο χιλιάδες δύο +MMIII δύο χιλιάδες τρία +MMIV δύο χιλιάδες τέσσερα +MMV δύο χιλιάδες πέντε +MMVI δύο χιλιάδες έξι +MMVII δύο χιλιάδες εφτά +MMVIII δύο χιλιάδες οχτώ +MMIX δύο χιλιάδες εννιά +MMX δύο χιλιάδες δέκα +MMXI δύο χιλιάδες έντεκα +MMXII δύο χιλιάδες δώδεκα +MMXIII δύο χιλιάδες δεκατρία +MMXIV δύο χιλιάδες δεκατέσσερα +MMXV δύο χιλιάδες δεκαπέντε +MMXVI δύο χιλιάδες δεκαέξι +MMXVII δύο χιλιάδες δεκαεφτά +MMXVIII δύο χιλιάδες δεκαοχτώ +MMXIX δύο χιλιάδες δεκαεννιά +MMXX δύο χιλιάδες είκοσι +MMXXI δύο χιλιάδες είκοσι ένα +MMXXII δύο χιλιάδες είκοσι δύο +MMXXIII δύο χιλιάδες είκοσι τρία +MMXXIV δύο χιλιάδες είκοσι τέσσερα +MMXXV δύο χιλιάδες είκοσι πέντε +MMXXVI δύο χιλιάδες είκοσι έξι +MMXXVII δύο χιλιάδες είκοσι εφτά +MMXXVIII δύο χιλιάδες είκοσι οχτώ +MMXXIX δύο χιλιάδες είκοσι εννιά +MMXXX δύο χιλιάδες τριάντα +MMXXXI δύο χιλιάδες τριάντα ένα +MMXXXII δύο χιλιάδες τριάντα δύο +MMXXXIII δύο χιλιάδες τριάντα τρία +MMXXXIV δύο χιλιάδες τριάντα τέσσερα +MMXXXV δύο χιλιάδες τριάντα πέντε +MMXXXVI δύο χιλιάδες τριάντα έξι +MMXXXVII δύο χιλιάδες τριάντα εφτά +MMXXXVIII δύο χιλιάδες τριάντα οχτώ +MMXXXIX δύο χιλιάδες τριάντα εννιά +MMXL δύο χιλιάδες σαράντα +MMXLI δύο χιλιάδες σαράντα ένα +MMXLII δύο χιλιάδες σαράντα δύο +MMXLIII δύο χιλιάδες σαράντα τρία +MMXLIV δύο χιλιάδες σαράντα τέσσερα +MMXLV δύο χιλιάδες σαράντα πέντε +MMXLVI δύο χιλιάδες σαράντα έξι +MMXLVII δύο χιλιάδες σαράντα εφτά +MMXLVIII δύο χιλιάδες σαράντα οχτώ +MMXLIX δύο χιλιάδες σαράντα εννιά +MML δύο χιλιάδες πενήντα +MMLI δύο χιλιάδες πενήντα ένα +MMLII δύο χιλιάδες πενήντα δύο +MMLIII δύο χιλιάδες πενήντα τρία +MMLIV δύο χιλιάδες πενήντα τέσσερα +MMLV δύο χιλιάδες πενήντα πέντε +MMLVI δύο χιλιάδες πενήντα έξι +MMLVII δύο χιλιάδες πενήντα εφτά +MMLVIII δύο χιλιάδες πενήντα οχτώ +MMLIX δύο χιλιάδες πενήντα εννιά +MMLX δύο χιλιάδες εξήντα +MMLXI δύο χιλιάδες εξήντα ένα +MMLXII δύο χιλιάδες εξήντα δύο +MMLXIII δύο χιλιάδες εξήντα τρία +MMLXIV δύο χιλιάδες εξήντα τέσσερα +MMLXV δύο χιλιάδες εξήντα πέντε +MMLXVI δύο χιλιάδες εξήντα έξι +MMLXVII δύο χιλιάδες εξήντα εφτά +MMLXVIII δύο χιλιάδες εξήντα οχτώ +MMLXIX δύο χιλιάδες εξήντα εννιά +MMLXX δύο χιλιάδες εβδομήντα +MMLXXI δύο χιλιάδες εβδομήντα ένα +MMLXXII δύο χιλιάδες εβδομήντα δύο +MMLXXIII δύο χιλιάδες εβδομήντα τρία +MMLXXIV δύο χιλιάδες εβδομήντα τέσσερα +MMLXXV δύο χιλιάδες εβδομήντα πέντε +MMLXXVI δύο χιλιάδες εβδομήντα έξι +MMLXXVII δύο χιλιάδες εβδομήντα εφτά +MMLXXVIII δύο χιλιάδες εβδομήντα οχτώ +MMLXXIX δύο χιλιάδες εβδομήντα εννιά +MMLXXX δύο χιλιάδες ογδόντα +MMLXXXI δύο χιλιάδες ογδόντα ένα +MMLXXXII δύο χιλιάδες ογδόντα δύο +MMLXXXIII δύο χιλιάδες ογδόντα τρία +MMLXXXIV δύο χιλιάδες ογδόντα τέσσερα +MMLXXXV δύο χιλιάδες ογδόντα πέντε +MMLXXXVI δύο χιλιάδες ογδόντα έξι +MMLXXXVII δύο χιλιάδες ογδόντα εφτά +MMLXXXVIII δύο χιλιάδες ογδόντα οχτώ +MMLXXXIX δύο χιλιάδες ογδόντα εννιά +MMXC δύο χιλιάδες ενενήντα +MMXCI δύο χιλιάδες ενενήντα ένα +MMXCII δύο χιλιάδες ενενήντα δύο +MMXCIII δύο χιλιάδες ενενήντα τρία +MMXCIV δύο χιλιάδες ενενήντα τέσσερα +MMXCV δύο χιλιάδες ενενήντα πέντε +MMXCVI δύο χιλιάδες ενενήντα έξι +MMXCVII δύο χιλιάδες ενενήντα εφτά +MMXCVIII δύο χιλιάδες ενενήντα οχτώ +MMXCIX δύο χιλιάδες ενενήντα εννιά +MMC δύο χιλιάδες εκατό +MMCI δύο χιλιάδες εκατόν ένα +MMCII δύο χιλιάδες εκατόν δύο +MMCIII δύο χιλιάδες εκατόν τρία +MMCIV δύο χιλιάδες εκατόν τέσσερα +MMCV δύο χιλιάδες εκατόν πέντε +MMCVI δύο χιλιάδες εκατόν έξι +MMCVII δύο χιλιάδες εκατόν εφτά +MMCVIII δύο χιλιάδες εκατόν οχτώ +MMCIX δύο χιλιάδες εκατόν εννιά +MMCX δύο χιλιάδες εκατόν δέκα +MMCXI δύο χιλιάδες εκατόν έντεκα +MMCXII δύο χιλιάδες εκατόν δώδεκα +MMCXIII δύο χιλιάδες εκατόν δεκατρία +MMCXIV δύο χιλιάδες εκατόν δεκατέσσερα +MMCXV δύο χιλιάδες εκατόν δεκαπέντε +MMCXVI δύο χιλιάδες εκατόν δεκαέξι +MMCXVII δύο χιλιάδες εκατόν δεκαεφτά +MMCXVIII δύο χιλιάδες εκατόν δεκαοχτώ +MMCXIX δύο χιλιάδες εκατόν δεκαεννιά +MMCXX δύο χιλιάδες εκατόν είκοσι +MMCXXI δύο χιλιάδες εκατόν είκοσι ένα +MMCXXII δύο χιλιάδες εκατόν είκοσι δύο +MMCXXIII δύο χιλιάδες εκατόν είκοσι τρία +MMCXXIV δύο χιλιάδες εκατόν είκοσι τέσσερα +MMCXXV δύο χιλιάδες εκατόν είκοσι πέντε +MMCXXVI δύο χιλιάδες εκατόν είκοσι έξι +MMCXXVII δύο χιλιάδες εκατόν είκοσι εφτά +MMCXXVIII δύο χιλιάδες εκατόν είκοσι οχτώ +MMCXXIX δύο χιλιάδες εκατόν είκοσι εννιά +MMCXXX δύο χιλιάδες εκατόν τριάντα +MMCXXXI δύο χιλιάδες εκατόν τριάντα ένα +MMCXXXII δύο χιλιάδες εκατόν τριάντα δύο +MMCXXXIII δύο χιλιάδες εκατόν τριάντα τρία +MMCXXXIV δύο χιλιάδες εκατόν τριάντα τέσσερα +MMCXXXV δύο χιλιάδες εκατόν τριάντα πέντε +MMCXXXVI δύο χιλιάδες εκατόν τριάντα έξι +MMCXXXVII δύο χιλιάδες εκατόν τριάντα εφτά +MMCXXXVIII δύο χιλιάδες εκατόν τριάντα οχτώ +MMCXXXIX δύο χιλιάδες εκατόν τριάντα εννιά +MMCXL δύο χιλιάδες εκατόν σαράντα +MMCXLI δύο χιλιάδες εκατόν σαράντα ένα +MMCXLII δύο χιλιάδες εκατόν σαράντα δύο +MMCXLIII δύο χιλιάδες εκατόν σαράντα τρία +MMCXLIV δύο χιλιάδες εκατόν σαράντα τέσσερα +MMCXLV δύο χιλιάδες εκατόν σαράντα πέντε +MMCXLVI δύο χιλιάδες εκατόν σαράντα έξι +MMCXLVII δύο χιλιάδες εκατόν σαράντα εφτά +MMCXLVIII δύο χιλιάδες εκατόν σαράντα οχτώ +MMCXLIX δύο χιλιάδες εκατόν σαράντα εννιά +MMCL δύο χιλιάδες εκατόν πενήντα +MMCLI δύο χιλιάδες εκατόν πενήντα ένα +MMCLII δύο χιλιάδες εκατόν πενήντα δύο +MMCLIII δύο χιλιάδες εκατόν πενήντα τρία +MMCLIV δύο χιλιάδες εκατόν πενήντα τέσσερα +MMCLV δύο χιλιάδες εκατόν πενήντα πέντε +MMCLVI δύο χιλιάδες εκατόν πενήντα έξι +MMCLVII δύο χιλιάδες εκατόν πενήντα εφτά +MMCLVIII δύο χιλιάδες εκατόν πενήντα οχτώ +MMCLIX δύο χιλιάδες εκατόν πενήντα εννιά +MMCLX δύο χιλιάδες εκατόν εξήντα +MMCLXI δύο χιλιάδες εκατόν εξήντα ένα +MMCLXII δύο χιλιάδες εκατόν εξήντα δύο +MMCLXIII δύο χιλιάδες εκατόν εξήντα τρία +MMCLXIV δύο χιλιάδες εκατόν εξήντα τέσσερα +MMCLXV δύο χιλιάδες εκατόν εξήντα πέντε +MMCLXVI δύο χιλιάδες εκατόν εξήντα έξι +MMCLXVII δύο χιλιάδες εκατόν εξήντα εφτά +MMCLXVIII δύο χιλιάδες εκατόν εξήντα οχτώ +MMCLXIX δύο χιλιάδες εκατόν εξήντα εννιά +MMCLXX δύο χιλιάδες εκατόν εβδομήντα +MMCLXXI δύο χιλιάδες εκατόν εβδομήντα ένα +MMCLXXII δύο χιλιάδες εκατόν εβδομήντα δύο +MMCLXXIII δύο χιλιάδες εκατόν εβδομήντα τρία +MMCLXXIV δύο χιλιάδες εκατόν εβδομήντα τέσσερα +MMCLXXV δύο χιλιάδες εκατόν εβδομήντα πέντε +MMCLXXVI δύο χιλιάδες εκατόν εβδομήντα έξι +MMCLXXVII δύο χιλιάδες εκατόν εβδομήντα εφτά +MMCLXXVIII δύο χιλιάδες εκατόν εβδομήντα οχτώ +MMCLXXIX δύο χιλιάδες εκατόν εβδομήντα εννιά +MMCLXXX δύο χιλιάδες εκατόν ογδόντα +MMCLXXXI δύο χιλιάδες εκατόν ογδόντα ένα +MMCLXXXII δύο χιλιάδες εκατόν ογδόντα δύο +MMCLXXXIII δύο χιλιάδες εκατόν ογδόντα τρία +MMCLXXXIV δύο χιλιάδες εκατόν ογδόντα τέσσερα +MMCLXXXV δύο χιλιάδες εκατόν ογδόντα πέντε +MMCLXXXVI δύο χιλιάδες εκατόν ογδόντα έξι +MMCLXXXVII δύο χιλιάδες εκατόν ογδόντα εφτά +MMCLXXXVIII δύο χιλιάδες εκατόν ογδόντα οχτώ +MMCLXXXIX δύο χιλιάδες εκατόν ογδόντα εννιά +MMCXC δύο χιλιάδες εκατόν ενενήντα +MMCXCI δύο χιλιάδες εκατόν ενενήντα ένα +MMCXCII δύο χιλιάδες εκατόν ενενήντα δύο +MMCXCIII δύο χιλιάδες εκατόν ενενήντα τρία +MMCXCIV δύο χιλιάδες εκατόν ενενήντα τέσσερα +MMCXCV δύο χιλιάδες εκατόν ενενήντα πέντε +MMCXCVI δύο χιλιάδες εκατόν ενενήντα έξι +MMCXCVII δύο χιλιάδες εκατόν ενενήντα εφτά +MMCXCVIII δύο χιλιάδες εκατόν ενενήντα οχτώ +MMCXCIX δύο χιλιάδες εκατόν ενενήντα εννιά +MMCC δύο χιλιάδες διακόσια +MMCCI δύο χιλιάδες διακόσια ένα +MMCCII δύο χιλιάδες διακόσια δύο +MMCCIII δύο χιλιάδες διακόσια τρία +MMCCIV δύο χιλιάδες διακόσια τέσσερα +MMCCV δύο χιλιάδες διακόσια πέντε +MMCCVI δύο χιλιάδες διακόσια έξι +MMCCVII δύο χιλιάδες διακόσια εφτά +MMCCVIII δύο χιλιάδες διακόσια οχτώ +MMCCIX δύο χιλιάδες διακόσια εννιά +MMCCX δύο χιλιάδες διακόσια δέκα +MMCCXI δύο χιλιάδες διακόσια έντεκα +MMCCXII δύο χιλιάδες διακόσια δώδεκα +MMCCXIII δύο χιλιάδες διακόσια δεκατρία +MMCCXIV δύο χιλιάδες διακόσια δεκατέσσερα +MMCCXV δύο χιλιάδες διακόσια δεκαπέντε +MMCCXVI δύο χιλιάδες διακόσια δεκαέξι +MMCCXVII δύο χιλιάδες διακόσια δεκαεφτά +MMCCXVIII δύο χιλιάδες διακόσια δεκαοχτώ +MMCCXIX δύο χιλιάδες διακόσια δεκαεννιά +MMCCXX δύο χιλιάδες διακόσια είκοσι +MMCCXXI δύο χιλιάδες διακόσια είκοσι ένα +MMCCXXII δύο χιλιάδες διακόσια είκοσι δύο +MMCCXXIII δύο χιλιάδες διακόσια είκοσι τρία +MMCCXXIV δύο χιλιάδες διακόσια είκοσι τέσσερα +MMCCXXV δύο χιλιάδες διακόσια είκοσι πέντε +MMCCXXVI δύο χιλιάδες διακόσια είκοσι έξι +MMCCXXVII δύο χιλιάδες διακόσια είκοσι εφτά +MMCCXXVIII δύο χιλιάδες διακόσια είκοσι οχτώ +MMCCXXIX δύο χιλιάδες διακόσια είκοσι εννιά +MMCCXXX δύο χιλιάδες διακόσια τριάντα +MMCCXXXI δύο χιλιάδες διακόσια τριάντα ένα +MMCCXXXII δύο χιλιάδες διακόσια τριάντα δύο +MMCCXXXIII δύο χιλιάδες διακόσια τριάντα τρία +MMCCXXXIV δύο χιλιάδες διακόσια τριάντα τέσσερα +MMCCXXXV δύο χιλιάδες διακόσια τριάντα πέντε +MMCCXXXVI δύο χιλιάδες διακόσια τριάντα έξι +MMCCXXXVII δύο χιλιάδες διακόσια τριάντα εφτά +MMCCXXXVIII δύο χιλιάδες διακόσια τριάντα οχτώ +MMCCXXXIX δύο χιλιάδες διακόσια τριάντα εννιά +MMCCXL δύο χιλιάδες διακόσια σαράντα +MMCCXLI δύο χιλιάδες διακόσια σαράντα ένα +MMCCXLII δύο χιλιάδες διακόσια σαράντα δύο +MMCCXLIII δύο χιλιάδες διακόσια σαράντα τρία +MMCCXLIV δύο χιλιάδες διακόσια σαράντα τέσσερα +MMCCXLV δύο χιλιάδες διακόσια σαράντα πέντε +MMCCXLVI δύο χιλιάδες διακόσια σαράντα έξι +MMCCXLVII δύο χιλιάδες διακόσια σαράντα εφτά +MMCCXLVIII δύο χιλιάδες διακόσια σαράντα οχτώ +MMCCXLIX δύο χιλιάδες διακόσια σαράντα εννιά +MMCCL δύο χιλιάδες διακόσια πενήντα +MMCCLI δύο χιλιάδες διακόσια πενήντα ένα +MMCCLII δύο χιλιάδες διακόσια πενήντα δύο +MMCCLIII δύο χιλιάδες διακόσια πενήντα τρία +MMCCLIV δύο χιλιάδες διακόσια πενήντα τέσσερα +MMCCLV δύο χιλιάδες διακόσια πενήντα πέντε +MMCCLVI δύο χιλιάδες διακόσια πενήντα έξι +MMCCLVII δύο χιλιάδες διακόσια πενήντα εφτά +MMCCLVIII δύο χιλιάδες διακόσια πενήντα οχτώ +MMCCLIX δύο χιλιάδες διακόσια πενήντα εννιά +MMCCLX δύο χιλιάδες διακόσια εξήντα +MMCCLXI δύο χιλιάδες διακόσια εξήντα ένα +MMCCLXII δύο χιλιάδες διακόσια εξήντα δύο +MMCCLXIII δύο χιλιάδες διακόσια εξήντα τρία +MMCCLXIV δύο χιλιάδες διακόσια εξήντα τέσσερα +MMCCLXV δύο χιλιάδες διακόσια εξήντα πέντε +MMCCLXVI δύο χιλιάδες διακόσια εξήντα έξι +MMCCLXVII δύο χιλιάδες διακόσια εξήντα εφτά +MMCCLXVIII δύο χιλιάδες διακόσια εξήντα οχτώ +MMCCLXIX δύο χιλιάδες διακόσια εξήντα εννιά +MMCCLXX δύο χιλιάδες διακόσια εβδομήντα +MMCCLXXI δύο χιλιάδες διακόσια εβδομήντα ένα +MMCCLXXII δύο χιλιάδες διακόσια εβδομήντα δύο +MMCCLXXIII δύο χιλιάδες διακόσια εβδομήντα τρία +MMCCLXXIV δύο χιλιάδες διακόσια εβδομήντα τέσσερα +MMCCLXXV δύο χιλιάδες διακόσια εβδομήντα πέντε +MMCCLXXVI δύο χιλιάδες διακόσια εβδομήντα έξι +MMCCLXXVII δύο χιλιάδες διακόσια εβδομήντα εφτά +MMCCLXXVIII δύο χιλιάδες διακόσια εβδομήντα οχτώ +MMCCLXXIX δύο χιλιάδες διακόσια εβδομήντα εννιά +MMCCLXXX δύο χιλιάδες διακόσια ογδόντα +MMCCLXXXI δύο χιλιάδες διακόσια ογδόντα ένα +MMCCLXXXII δύο χιλιάδες διακόσια ογδόντα δύο +MMCCLXXXIII δύο χιλιάδες διακόσια ογδόντα τρία +MMCCLXXXIV δύο χιλιάδες διακόσια ογδόντα τέσσερα +MMCCLXXXV δύο χιλιάδες διακόσια ογδόντα πέντε +MMCCLXXXVI δύο χιλιάδες διακόσια ογδόντα έξι +MMCCLXXXVII δύο χιλιάδες διακόσια ογδόντα εφτά +MMCCLXXXVIII δύο χιλιάδες διακόσια ογδόντα οχτώ +MMCCLXXXIX δύο χιλιάδες διακόσια ογδόντα εννιά +MMCCXC δύο χιλιάδες διακόσια ενενήντα +MMCCXCI δύο χιλιάδες διακόσια ενενήντα ένα +MMCCXCII δύο χιλιάδες διακόσια ενενήντα δύο +MMCCXCIII δύο χιλιάδες διακόσια ενενήντα τρία +MMCCXCIV δύο χιλιάδες διακόσια ενενήντα τέσσερα +MMCCXCV δύο χιλιάδες διακόσια ενενήντα πέντε +MMCCXCVI δύο χιλιάδες διακόσια ενενήντα έξι +MMCCXCVII δύο χιλιάδες διακόσια ενενήντα εφτά +MMCCXCVIII δύο χιλιάδες διακόσια ενενήντα οχτώ +MMCCXCIX δύο χιλιάδες διακόσια ενενήντα εννιά +MMCCC δύο χιλιάδες τριακόσια +MMCCCI δύο χιλιάδες τριακόσια ένα +MMCCCII δύο χιλιάδες τριακόσια δύο +MMCCCIII δύο χιλιάδες τριακόσια τρία +MMCCCIV δύο χιλιάδες τριακόσια τέσσερα +MMCCCV δύο χιλιάδες τριακόσια πέντε +MMCCCVI δύο χιλιάδες τριακόσια έξι +MMCCCVII δύο χιλιάδες τριακόσια εφτά +MMCCCVIII δύο χιλιάδες τριακόσια οχτώ +MMCCCIX δύο χιλιάδες τριακόσια εννιά +MMCCCX δύο χιλιάδες τριακόσια δέκα +MMCCCXI δύο χιλιάδες τριακόσια έντεκα +MMCCCXII δύο χιλιάδες τριακόσια δώδεκα +MMCCCXIII δύο χιλιάδες τριακόσια δεκατρία +MMCCCXIV δύο χιλιάδες τριακόσια δεκατέσσερα +MMCCCXV δύο χιλιάδες τριακόσια δεκαπέντε +MMCCCXVI δύο χιλιάδες τριακόσια δεκαέξι +MMCCCXVII δύο χιλιάδες τριακόσια δεκαεφτά +MMCCCXVIII δύο χιλιάδες τριακόσια δεκαοχτώ +MMCCCXIX δύο χιλιάδες τριακόσια δεκαεννιά +MMCCCXX δύο χιλιάδες τριακόσια είκοσι +MMCCCXXI δύο χιλιάδες τριακόσια είκοσι ένα +MMCCCXXII δύο χιλιάδες τριακόσια είκοσι δύο +MMCCCXXIII δύο χιλιάδες τριακόσια είκοσι τρία +MMCCCXXIV δύο χιλιάδες τριακόσια είκοσι τέσσερα +MMCCCXXV δύο χιλιάδες τριακόσια είκοσι πέντε +MMCCCXXVI δύο χιλιάδες τριακόσια είκοσι έξι +MMCCCXXVII δύο χιλιάδες τριακόσια είκοσι εφτά +MMCCCXXVIII δύο χιλιάδες τριακόσια είκοσι οχτώ +MMCCCXXIX δύο χιλιάδες τριακόσια είκοσι εννιά +MMCCCXXX δύο χιλιάδες τριακόσια τριάντα +MMCCCXXXI δύο χιλιάδες τριακόσια τριάντα ένα +MMCCCXXXII δύο χιλιάδες τριακόσια τριάντα δύο +MMCCCXXXIII δύο χιλιάδες τριακόσια τριάντα τρία +MMCCCXXXIV δύο χιλιάδες τριακόσια τριάντα τέσσερα +MMCCCXXXV δύο χιλιάδες τριακόσια τριάντα πέντε +MMCCCXXXVI δύο χιλιάδες τριακόσια τριάντα έξι +MMCCCXXXVII δύο χιλιάδες τριακόσια τριάντα εφτά +MMCCCXXXVIII δύο χιλιάδες τριακόσια τριάντα οχτώ +MMCCCXXXIX δύο χιλιάδες τριακόσια τριάντα εννιά +MMCCCXL δύο χιλιάδες τριακόσια σαράντα +MMCCCXLI δύο χιλιάδες τριακόσια σαράντα ένα +MMCCCXLII δύο χιλιάδες τριακόσια σαράντα δύο +MMCCCXLIII δύο χιλιάδες τριακόσια σαράντα τρία +MMCCCXLIV δύο χιλιάδες τριακόσια σαράντα τέσσερα +MMCCCXLV δύο χιλιάδες τριακόσια σαράντα πέντε +MMCCCXLVI δύο χιλιάδες τριακόσια σαράντα έξι +MMCCCXLVII δύο χιλιάδες τριακόσια σαράντα εφτά +MMCCCXLVIII δύο χιλιάδες τριακόσια σαράντα οχτώ +MMCCCXLIX δύο χιλιάδες τριακόσια σαράντα εννιά +MMCCCL δύο χιλιάδες τριακόσια πενήντα +MMCCCLI δύο χιλιάδες τριακόσια πενήντα ένα +MMCCCLII δύο χιλιάδες τριακόσια πενήντα δύο +MMCCCLIII δύο χιλιάδες τριακόσια πενήντα τρία +MMCCCLIV δύο χιλιάδες τριακόσια πενήντα τέσσερα +MMCCCLV δύο χιλιάδες τριακόσια πενήντα πέντε +MMCCCLVI δύο χιλιάδες τριακόσια πενήντα έξι +MMCCCLVII δύο χιλιάδες τριακόσια πενήντα εφτά +MMCCCLVIII δύο χιλιάδες τριακόσια πενήντα οχτώ +MMCCCLIX δύο χιλιάδες τριακόσια πενήντα εννιά +MMCCCLX δύο χιλιάδες τριακόσια εξήντα +MMCCCLXI δύο χιλιάδες τριακόσια εξήντα ένα +MMCCCLXII δύο χιλιάδες τριακόσια εξήντα δύο +MMCCCLXIII δύο χιλιάδες τριακόσια εξήντα τρία +MMCCCLXIV δύο χιλιάδες τριακόσια εξήντα τέσσερα +MMCCCLXV δύο χιλιάδες τριακόσια εξήντα πέντε +MMCCCLXVI δύο χιλιάδες τριακόσια εξήντα έξι +MMCCCLXVII δύο χιλιάδες τριακόσια εξήντα εφτά +MMCCCLXVIII δύο χιλιάδες τριακόσια εξήντα οχτώ +MMCCCLXIX δύο χιλιάδες τριακόσια εξήντα εννιά +MMCCCLXX δύο χιλιάδες τριακόσια εβδομήντα +MMCCCLXXI δύο χιλιάδες τριακόσια εβδομήντα ένα +MMCCCLXXII δύο χιλιάδες τριακόσια εβδομήντα δύο +MMCCCLXXIII δύο χιλιάδες τριακόσια εβδομήντα τρία +MMCCCLXXIV δύο χιλιάδες τριακόσια εβδομήντα τέσσερα +MMCCCLXXV δύο χιλιάδες τριακόσια εβδομήντα πέντε +MMCCCLXXVI δύο χιλιάδες τριακόσια εβδομήντα έξι +MMCCCLXXVII δύο χιλιάδες τριακόσια εβδομήντα εφτά +MMCCCLXXVIII δύο χιλιάδες τριακόσια εβδομήντα οχτώ +MMCCCLXXIX δύο χιλιάδες τριακόσια εβδομήντα εννιά +MMCCCLXXX δύο χιλιάδες τριακόσια ογδόντα +MMCCCLXXXI δύο χιλιάδες τριακόσια ογδόντα ένα +MMCCCLXXXII δύο χιλιάδες τριακόσια ογδόντα δύο +MMCCCLXXXIII δύο χιλιάδες τριακόσια ογδόντα τρία +MMCCCLXXXIV δύο χιλιάδες τριακόσια ογδόντα τέσσερα +MMCCCLXXXV δύο χιλιάδες τριακόσια ογδόντα πέντε +MMCCCLXXXVI δύο χιλιάδες τριακόσια ογδόντα έξι +MMCCCLXXXVII δύο χιλιάδες τριακόσια ογδόντα εφτά +MMCCCLXXXVIII δύο χιλιάδες τριακόσια ογδόντα οχτώ +MMCCCLXXXIX δύο χιλιάδες τριακόσια ογδόντα εννιά +MMCCCXC δύο χιλιάδες τριακόσια ενενήντα +MMCCCXCI δύο χιλιάδες τριακόσια ενενήντα ένα +MMCCCXCII δύο χιλιάδες τριακόσια ενενήντα δύο +MMCCCXCIII δύο χιλιάδες τριακόσια ενενήντα τρία +MMCCCXCIV δύο χιλιάδες τριακόσια ενενήντα τέσσερα +MMCCCXCV δύο χιλιάδες τριακόσια ενενήντα πέντε +MMCCCXCVI δύο χιλιάδες τριακόσια ενενήντα έξι +MMCCCXCVII δύο χιλιάδες τριακόσια ενενήντα εφτά +MMCCCXCVIII δύο χιλιάδες τριακόσια ενενήντα οχτώ +MMCCCXCIX δύο χιλιάδες τριακόσια ενενήντα εννιά +MMCD δύο χιλιάδες τετρακόσια +MMCDI δύο χιλιάδες τετρακόσια ένα +MMCDII δύο χιλιάδες τετρακόσια δύο +MMCDIII δύο χιλιάδες τετρακόσια τρία +MMCDIV δύο χιλιάδες τετρακόσια τέσσερα +MMCDV δύο χιλιάδες τετρακόσια πέντε +MMCDVI δύο χιλιάδες τετρακόσια έξι +MMCDVII δύο χιλιάδες τετρακόσια εφτά +MMCDVIII δύο χιλιάδες τετρακόσια οχτώ +MMCDIX δύο χιλιάδες τετρακόσια εννιά +MMCDX δύο χιλιάδες τετρακόσια δέκα +MMCDXI δύο χιλιάδες τετρακόσια έντεκα +MMCDXII δύο χιλιάδες τετρακόσια δώδεκα +MMCDXIII δύο χιλιάδες τετρακόσια δεκατρία +MMCDXIV δύο χιλιάδες τετρακόσια δεκατέσσερα +MMCDXV δύο χιλιάδες τετρακόσια δεκαπέντε +MMCDXVI δύο χιλιάδες τετρακόσια δεκαέξι +MMCDXVII δύο χιλιάδες τετρακόσια δεκαεφτά +MMCDXVIII δύο χιλιάδες τετρακόσια δεκαοχτώ +MMCDXIX δύο χιλιάδες τετρακόσια δεκαεννιά +MMCDXX δύο χιλιάδες τετρακόσια είκοσι +MMCDXXI δύο χιλιάδες τετρακόσια είκοσι ένα +MMCDXXII δύο χιλιάδες τετρακόσια είκοσι δύο +MMCDXXIII δύο χιλιάδες τετρακόσια είκοσι τρία +MMCDXXIV δύο χιλιάδες τετρακόσια είκοσι τέσσερα +MMCDXXV δύο χιλιάδες τετρακόσια είκοσι πέντε +MMCDXXVI δύο χιλιάδες τετρακόσια είκοσι έξι +MMCDXXVII δύο χιλιάδες τετρακόσια είκοσι εφτά +MMCDXXVIII δύο χιλιάδες τετρακόσια είκοσι οχτώ +MMCDXXIX δύο χιλιάδες τετρακόσια είκοσι εννιά +MMCDXXX δύο χιλιάδες τετρακόσια τριάντα +MMCDXXXI δύο χιλιάδες τετρακόσια τριάντα ένα +MMCDXXXII δύο χιλιάδες τετρακόσια τριάντα δύο +MMCDXXXIII δύο χιλιάδες τετρακόσια τριάντα τρία +MMCDXXXIV δύο χιλιάδες τετρακόσια τριάντα τέσσερα +MMCDXXXV δύο χιλιάδες τετρακόσια τριάντα πέντε +MMCDXXXVI δύο χιλιάδες τετρακόσια τριάντα έξι +MMCDXXXVII δύο χιλιάδες τετρακόσια τριάντα εφτά +MMCDXXXVIII δύο χιλιάδες τετρακόσια τριάντα οχτώ +MMCDXXXIX δύο χιλιάδες τετρακόσια τριάντα εννιά +MMCDXL δύο χιλιάδες τετρακόσια σαράντα +MMCDXLI δύο χιλιάδες τετρακόσια σαράντα ένα +MMCDXLII δύο χιλιάδες τετρακόσια σαράντα δύο +MMCDXLIII δύο χιλιάδες τετρακόσια σαράντα τρία +MMCDXLIV δύο χιλιάδες τετρακόσια σαράντα τέσσερα +MMCDXLV δύο χιλιάδες τετρακόσια σαράντα πέντε +MMCDXLVI δύο χιλιάδες τετρακόσια σαράντα έξι +MMCDXLVII δύο χιλιάδες τετρακόσια σαράντα εφτά +MMCDXLVIII δύο χιλιάδες τετρακόσια σαράντα οχτώ +MMCDXLIX δύο χιλιάδες τετρακόσια σαράντα εννιά +MMCDL δύο χιλιάδες τετρακόσια πενήντα +MMCDLI δύο χιλιάδες τετρακόσια πενήντα ένα +MMCDLII δύο χιλιάδες τετρακόσια πενήντα δύο +MMCDLIII δύο χιλιάδες τετρακόσια πενήντα τρία +MMCDLIV δύο χιλιάδες τετρακόσια πενήντα τέσσερα +MMCDLV δύο χιλιάδες τετρακόσια πενήντα πέντε +MMCDLVI δύο χιλιάδες τετρακόσια πενήντα έξι +MMCDLVII δύο χιλιάδες τετρακόσια πενήντα εφτά +MMCDLVIII δύο χιλιάδες τετρακόσια πενήντα οχτώ +MMCDLIX δύο χιλιάδες τετρακόσια πενήντα εννιά +MMCDLX δύο χιλιάδες τετρακόσια εξήντα +MMCDLXI δύο χιλιάδες τετρακόσια εξήντα ένα +MMCDLXII δύο χιλιάδες τετρακόσια εξήντα δύο +MMCDLXIII δύο χιλιάδες τετρακόσια εξήντα τρία +MMCDLXIV δύο χιλιάδες τετρακόσια εξήντα τέσσερα +MMCDLXV δύο χιλιάδες τετρακόσια εξήντα πέντε +MMCDLXVI δύο χιλιάδες τετρακόσια εξήντα έξι +MMCDLXVII δύο χιλιάδες τετρακόσια εξήντα εφτά +MMCDLXVIII δύο χιλιάδες τετρακόσια εξήντα οχτώ +MMCDLXIX δύο χιλιάδες τετρακόσια εξήντα εννιά +MMCDLXX δύο χιλιάδες τετρακόσια εβδομήντα +MMCDLXXI δύο χιλιάδες τετρακόσια εβδομήντα ένα +MMCDLXXII δύο χιλιάδες τετρακόσια εβδομήντα δύο +MMCDLXXIII δύο χιλιάδες τετρακόσια εβδομήντα τρία +MMCDLXXIV δύο χιλιάδες τετρακόσια εβδομήντα τέσσερα +MMCDLXXV δύο χιλιάδες τετρακόσια εβδομήντα πέντε +MMCDLXXVI δύο χιλιάδες τετρακόσια εβδομήντα έξι +MMCDLXXVII δύο χιλιάδες τετρακόσια εβδομήντα εφτά +MMCDLXXVIII δύο χιλιάδες τετρακόσια εβδομήντα οχτώ +MMCDLXXIX δύο χιλιάδες τετρακόσια εβδομήντα εννιά +MMCDLXXX δύο χιλιάδες τετρακόσια ογδόντα +MMCDLXXXI δύο χιλιάδες τετρακόσια ογδόντα ένα +MMCDLXXXII δύο χιλιάδες τετρακόσια ογδόντα δύο +MMCDLXXXIII δύο χιλιάδες τετρακόσια ογδόντα τρία +MMCDLXXXIV δύο χιλιάδες τετρακόσια ογδόντα τέσσερα +MMCDLXXXV δύο χιλιάδες τετρακόσια ογδόντα πέντε +MMCDLXXXVI δύο χιλιάδες τετρακόσια ογδόντα έξι +MMCDLXXXVII δύο χιλιάδες τετρακόσια ογδόντα εφτά +MMCDLXXXVIII δύο χιλιάδες τετρακόσια ογδόντα οχτώ +MMCDLXXXIX δύο χιλιάδες τετρακόσια ογδόντα εννιά +MMCDXC δύο χιλιάδες τετρακόσια ενενήντα +MMCDXCI δύο χιλιάδες τετρακόσια ενενήντα ένα +MMCDXCII δύο χιλιάδες τετρακόσια ενενήντα δύο +MMCDXCIII δύο χιλιάδες τετρακόσια ενενήντα τρία +MMCDXCIV δύο χιλιάδες τετρακόσια ενενήντα τέσσερα +MMCDXCV δύο χιλιάδες τετρακόσια ενενήντα πέντε +MMCDXCVI δύο χιλιάδες τετρακόσια ενενήντα έξι +MMCDXCVII δύο χιλιάδες τετρακόσια ενενήντα εφτά +MMCDXCVIII δύο χιλιάδες τετρακόσια ενενήντα οχτώ +MMCDXCIX δύο χιλιάδες τετρακόσια ενενήντα εννιά +MMD δύο χιλιάδες πεντακόσια +MMDI δύο χιλιάδες πεντακόσια ένα +MMDII δύο χιλιάδες πεντακόσια δύο +MMDIII δύο χιλιάδες πεντακόσια τρία +MMDIV δύο χιλιάδες πεντακόσια τέσσερα +MMDV δύο χιλιάδες πεντακόσια πέντε +MMDVI δύο χιλιάδες πεντακόσια έξι +MMDVII δύο χιλιάδες πεντακόσια εφτά +MMDVIII δύο χιλιάδες πεντακόσια οχτώ +MMDIX δύο χιλιάδες πεντακόσια εννιά +MMDX δύο χιλιάδες πεντακόσια δέκα +MMDXI δύο χιλιάδες πεντακόσια έντεκα +MMDXII δύο χιλιάδες πεντακόσια δώδεκα +MMDXIII δύο χιλιάδες πεντακόσια δεκατρία +MMDXIV δύο χιλιάδες πεντακόσια δεκατέσσερα +MMDXV δύο χιλιάδες πεντακόσια δεκαπέντε +MMDXVI δύο χιλιάδες πεντακόσια δεκαέξι +MMDXVII δύο χιλιάδες πεντακόσια δεκαεφτά +MMDXVIII δύο χιλιάδες πεντακόσια δεκαοχτώ +MMDXIX δύο χιλιάδες πεντακόσια δεκαεννιά +MMDXX δύο χιλιάδες πεντακόσια είκοσι +MMDXXI δύο χιλιάδες πεντακόσια είκοσι ένα +MMDXXII δύο χιλιάδες πεντακόσια είκοσι δύο +MMDXXIII δύο χιλιάδες πεντακόσια είκοσι τρία +MMDXXIV δύο χιλιάδες πεντακόσια είκοσι τέσσερα +MMDXXV δύο χιλιάδες πεντακόσια είκοσι πέντε +MMDXXVI δύο χιλιάδες πεντακόσια είκοσι έξι +MMDXXVII δύο χιλιάδες πεντακόσια είκοσι εφτά +MMDXXVIII δύο χιλιάδες πεντακόσια είκοσι οχτώ +MMDXXIX δύο χιλιάδες πεντακόσια είκοσι εννιά +MMDXXX δύο χιλιάδες πεντακόσια τριάντα +MMDXXXI δύο χιλιάδες πεντακόσια τριάντα ένα +MMDXXXII δύο χιλιάδες πεντακόσια τριάντα δύο +MMDXXXIII δύο χιλιάδες πεντακόσια τριάντα τρία +MMDXXXIV δύο χιλιάδες πεντακόσια τριάντα τέσσερα +MMDXXXV δύο χιλιάδες πεντακόσια τριάντα πέντε +MMDXXXVI δύο χιλιάδες πεντακόσια τριάντα έξι +MMDXXXVII δύο χιλιάδες πεντακόσια τριάντα εφτά +MMDXXXVIII δύο χιλιάδες πεντακόσια τριάντα οχτώ +MMDXXXIX δύο χιλιάδες πεντακόσια τριάντα εννιά +MMDXL δύο χιλιάδες πεντακόσια σαράντα +MMDXLI δύο χιλιάδες πεντακόσια σαράντα ένα +MMDXLII δύο χιλιάδες πεντακόσια σαράντα δύο +MMDXLIII δύο χιλιάδες πεντακόσια σαράντα τρία +MMDXLIV δύο χιλιάδες πεντακόσια σαράντα τέσσερα +MMDXLV δύο χιλιάδες πεντακόσια σαράντα πέντε +MMDXLVI δύο χιλιάδες πεντακόσια σαράντα έξι +MMDXLVII δύο χιλιάδες πεντακόσια σαράντα εφτά +MMDXLVIII δύο χιλιάδες πεντακόσια σαράντα οχτώ +MMDXLIX δύο χιλιάδες πεντακόσια σαράντα εννιά +MMDL δύο χιλιάδες πεντακόσια πενήντα +MMDLI δύο χιλιάδες πεντακόσια πενήντα ένα +MMDLII δύο χιλιάδες πεντακόσια πενήντα δύο +MMDLIII δύο χιλιάδες πεντακόσια πενήντα τρία +MMDLIV δύο χιλιάδες πεντακόσια πενήντα τέσσερα +MMDLV δύο χιλιάδες πεντακόσια πενήντα πέντε +MMDLVI δύο χιλιάδες πεντακόσια πενήντα έξι +MMDLVII δύο χιλιάδες πεντακόσια πενήντα εφτά +MMDLVIII δύο χιλιάδες πεντακόσια πενήντα οχτώ +MMDLIX δύο χιλιάδες πεντακόσια πενήντα εννιά +MMDLX δύο χιλιάδες πεντακόσια εξήντα +MMDLXI δύο χιλιάδες πεντακόσια εξήντα ένα +MMDLXII δύο χιλιάδες πεντακόσια εξήντα δύο +MMDLXIII δύο χιλιάδες πεντακόσια εξήντα τρία +MMDLXIV δύο χιλιάδες πεντακόσια εξήντα τέσσερα +MMDLXV δύο χιλιάδες πεντακόσια εξήντα πέντε +MMDLXVI δύο χιλιάδες πεντακόσια εξήντα έξι +MMDLXVII δύο χιλιάδες πεντακόσια εξήντα εφτά +MMDLXVIII δύο χιλιάδες πεντακόσια εξήντα οχτώ +MMDLXIX δύο χιλιάδες πεντακόσια εξήντα εννιά +MMDLXX δύο χιλιάδες πεντακόσια εβδομήντα +MMDLXXI δύο χιλιάδες πεντακόσια εβδομήντα ένα +MMDLXXII δύο χιλιάδες πεντακόσια εβδομήντα δύο +MMDLXXIII δύο χιλιάδες πεντακόσια εβδομήντα τρία +MMDLXXIV δύο χιλιάδες πεντακόσια εβδομήντα τέσσερα +MMDLXXV δύο χιλιάδες πεντακόσια εβδομήντα πέντε +MMDLXXVI δύο χιλιάδες πεντακόσια εβδομήντα έξι +MMDLXXVII δύο χιλιάδες πεντακόσια εβδομήντα εφτά +MMDLXXVIII δύο χιλιάδες πεντακόσια εβδομήντα οχτώ +MMDLXXIX δύο χιλιάδες πεντακόσια εβδομήντα εννιά +MMDLXXX δύο χιλιάδες πεντακόσια ογδόντα +MMDLXXXI δύο χιλιάδες πεντακόσια ογδόντα ένα +MMDLXXXII δύο χιλιάδες πεντακόσια ογδόντα δύο +MMDLXXXIII δύο χιλιάδες πεντακόσια ογδόντα τρία +MMDLXXXIV δύο χιλιάδες πεντακόσια ογδόντα τέσσερα +MMDLXXXV δύο χιλιάδες πεντακόσια ογδόντα πέντε +MMDLXXXVI δύο χιλιάδες πεντακόσια ογδόντα έξι +MMDLXXXVII δύο χιλιάδες πεντακόσια ογδόντα εφτά +MMDLXXXVIII δύο χιλιάδες πεντακόσια ογδόντα οχτώ +MMDLXXXIX δύο χιλιάδες πεντακόσια ογδόντα εννιά +MMDXC δύο χιλιάδες πεντακόσια ενενήντα +MMDXCI δύο χιλιάδες πεντακόσια ενενήντα ένα +MMDXCII δύο χιλιάδες πεντακόσια ενενήντα δύο +MMDXCIII δύο χιλιάδες πεντακόσια ενενήντα τρία +MMDXCIV δύο χιλιάδες πεντακόσια ενενήντα τέσσερα +MMDXCV δύο χιλιάδες πεντακόσια ενενήντα πέντε +MMDXCVI δύο χιλιάδες πεντακόσια ενενήντα έξι +MMDXCVII δύο χιλιάδες πεντακόσια ενενήντα εφτά +MMDXCVIII δύο χιλιάδες πεντακόσια ενενήντα οχτώ +MMDXCIX δύο χιλιάδες πεντακόσια ενενήντα εννιά +MMDC δύο χιλιάδες εξακόσια +MMDCI δύο χιλιάδες εξακόσια ένα +MMDCII δύο χιλιάδες εξακόσια δύο +MMDCIII δύο χιλιάδες εξακόσια τρία +MMDCIV δύο χιλιάδες εξακόσια τέσσερα +MMDCV δύο χιλιάδες εξακόσια πέντε +MMDCVI δύο χιλιάδες εξακόσια έξι +MMDCVII δύο χιλιάδες εξακόσια εφτά +MMDCVIII δύο χιλιάδες εξακόσια οχτώ +MMDCIX δύο χιλιάδες εξακόσια εννιά +MMDCX δύο χιλιάδες εξακόσια δέκα +MMDCXI δύο χιλιάδες εξακόσια έντεκα +MMDCXII δύο χιλιάδες εξακόσια δώδεκα +MMDCXIII δύο χιλιάδες εξακόσια δεκατρία +MMDCXIV δύο χιλιάδες εξακόσια δεκατέσσερα +MMDCXV δύο χιλιάδες εξακόσια δεκαπέντε +MMDCXVI δύο χιλιάδες εξακόσια δεκαέξι +MMDCXVII δύο χιλιάδες εξακόσια δεκαεφτά +MMDCXVIII δύο χιλιάδες εξακόσια δεκαοχτώ +MMDCXIX δύο χιλιάδες εξακόσια δεκαεννιά +MMDCXX δύο χιλιάδες εξακόσια είκοσι +MMDCXXI δύο χιλιάδες εξακόσια είκοσι ένα +MMDCXXII δύο χιλιάδες εξακόσια είκοσι δύο +MMDCXXIII δύο χιλιάδες εξακόσια είκοσι τρία +MMDCXXIV δύο χιλιάδες εξακόσια είκοσι τέσσερα +MMDCXXV δύο χιλιάδες εξακόσια είκοσι πέντε +MMDCXXVI δύο χιλιάδες εξακόσια είκοσι έξι +MMDCXXVII δύο χιλιάδες εξακόσια είκοσι εφτά +MMDCXXVIII δύο χιλιάδες εξακόσια είκοσι οχτώ +MMDCXXIX δύο χιλιάδες εξακόσια είκοσι εννιά +MMDCXXX δύο χιλιάδες εξακόσια τριάντα +MMDCXXXI δύο χιλιάδες εξακόσια τριάντα ένα +MMDCXXXII δύο χιλιάδες εξακόσια τριάντα δύο +MMDCXXXIII δύο χιλιάδες εξακόσια τριάντα τρία +MMDCXXXIV δύο χιλιάδες εξακόσια τριάντα τέσσερα +MMDCXXXV δύο χιλιάδες εξακόσια τριάντα πέντε +MMDCXXXVI δύο χιλιάδες εξακόσια τριάντα έξι +MMDCXXXVII δύο χιλιάδες εξακόσια τριάντα εφτά +MMDCXXXVIII δύο χιλιάδες εξακόσια τριάντα οχτώ +MMDCXXXIX δύο χιλιάδες εξακόσια τριάντα εννιά +MMDCXL δύο χιλιάδες εξακόσια σαράντα +MMDCXLI δύο χιλιάδες εξακόσια σαράντα ένα +MMDCXLII δύο χιλιάδες εξακόσια σαράντα δύο +MMDCXLIII δύο χιλιάδες εξακόσια σαράντα τρία +MMDCXLIV δύο χιλιάδες εξακόσια σαράντα τέσσερα +MMDCXLV δύο χιλιάδες εξακόσια σαράντα πέντε +MMDCXLVI δύο χιλιάδες εξακόσια σαράντα έξι +MMDCXLVII δύο χιλιάδες εξακόσια σαράντα εφτά +MMDCXLVIII δύο χιλιάδες εξακόσια σαράντα οχτώ +MMDCXLIX δύο χιλιάδες εξακόσια σαράντα εννιά +MMDCL δύο χιλιάδες εξακόσια πενήντα +MMDCLI δύο χιλιάδες εξακόσια πενήντα ένα +MMDCLII δύο χιλιάδες εξακόσια πενήντα δύο +MMDCLIII δύο χιλιάδες εξακόσια πενήντα τρία +MMDCLIV δύο χιλιάδες εξακόσια πενήντα τέσσερα +MMDCLV δύο χιλιάδες εξακόσια πενήντα πέντε +MMDCLVI δύο χιλιάδες εξακόσια πενήντα έξι +MMDCLVII δύο χιλιάδες εξακόσια πενήντα εφτά +MMDCLVIII δύο χιλιάδες εξακόσια πενήντα οχτώ +MMDCLIX δύο χιλιάδες εξακόσια πενήντα εννιά +MMDCLX δύο χιλιάδες εξακόσια εξήντα +MMDCLXI δύο χιλιάδες εξακόσια εξήντα ένα +MMDCLXII δύο χιλιάδες εξακόσια εξήντα δύο +MMDCLXIII δύο χιλιάδες εξακόσια εξήντα τρία +MMDCLXIV δύο χιλιάδες εξακόσια εξήντα τέσσερα +MMDCLXV δύο χιλιάδες εξακόσια εξήντα πέντε +MMDCLXVI δύο χιλιάδες εξακόσια εξήντα έξι +MMDCLXVII δύο χιλιάδες εξακόσια εξήντα εφτά +MMDCLXVIII δύο χιλιάδες εξακόσια εξήντα οχτώ +MMDCLXIX δύο χιλιάδες εξακόσια εξήντα εννιά +MMDCLXX δύο χιλιάδες εξακόσια εβδομήντα +MMDCLXXI δύο χιλιάδες εξακόσια εβδομήντα ένα +MMDCLXXII δύο χιλιάδες εξακόσια εβδομήντα δύο +MMDCLXXIII δύο χιλιάδες εξακόσια εβδομήντα τρία +MMDCLXXIV δύο χιλιάδες εξακόσια εβδομήντα τέσσερα +MMDCLXXV δύο χιλιάδες εξακόσια εβδομήντα πέντε +MMDCLXXVI δύο χιλιάδες εξακόσια εβδομήντα έξι +MMDCLXXVII δύο χιλιάδες εξακόσια εβδομήντα εφτά +MMDCLXXVIII δύο χιλιάδες εξακόσια εβδομήντα οχτώ +MMDCLXXIX δύο χιλιάδες εξακόσια εβδομήντα εννιά +MMDCLXXX δύο χιλιάδες εξακόσια ογδόντα +MMDCLXXXI δύο χιλιάδες εξακόσια ογδόντα ένα +MMDCLXXXII δύο χιλιάδες εξακόσια ογδόντα δύο +MMDCLXXXIII δύο χιλιάδες εξακόσια ογδόντα τρία +MMDCLXXXIV δύο χιλιάδες εξακόσια ογδόντα τέσσερα +MMDCLXXXV δύο χιλιάδες εξακόσια ογδόντα πέντε +MMDCLXXXVI δύο χιλιάδες εξακόσια ογδόντα έξι +MMDCLXXXVII δύο χιλιάδες εξακόσια ογδόντα εφτά +MMDCLXXXVIII δύο χιλιάδες εξακόσια ογδόντα οχτώ +MMDCLXXXIX δύο χιλιάδες εξακόσια ογδόντα εννιά +MMDCXC δύο χιλιάδες εξακόσια ενενήντα +MMDCXCI δύο χιλιάδες εξακόσια ενενήντα ένα +MMDCXCII δύο χιλιάδες εξακόσια ενενήντα δύο +MMDCXCIII δύο χιλιάδες εξακόσια ενενήντα τρία +MMDCXCIV δύο χιλιάδες εξακόσια ενενήντα τέσσερα +MMDCXCV δύο χιλιάδες εξακόσια ενενήντα πέντε +MMDCXCVI δύο χιλιάδες εξακόσια ενενήντα έξι +MMDCXCVII δύο χιλιάδες εξακόσια ενενήντα εφτά +MMDCXCVIII δύο χιλιάδες εξακόσια ενενήντα οχτώ +MMDCXCIX δύο χιλιάδες εξακόσια ενενήντα εννιά +MMDCC δύο χιλιάδες εφτακόσια +MMDCCI δύο χιλιάδες εφτακόσια ένα +MMDCCII δύο χιλιάδες εφτακόσια δύο +MMDCCIII δύο χιλιάδες εφτακόσια τρία +MMDCCIV δύο χιλιάδες εφτακόσια τέσσερα +MMDCCV δύο χιλιάδες εφτακόσια πέντε +MMDCCVI δύο χιλιάδες εφτακόσια έξι +MMDCCVII δύο χιλιάδες εφτακόσια εφτά +MMDCCVIII δύο χιλιάδες εφτακόσια οχτώ +MMDCCIX δύο χιλιάδες εφτακόσια εννιά +MMDCCX δύο χιλιάδες εφτακόσια δέκα +MMDCCXI δύο χιλιάδες εφτακόσια έντεκα +MMDCCXII δύο χιλιάδες εφτακόσια δώδεκα +MMDCCXIII δύο χιλιάδες εφτακόσια δεκατρία +MMDCCXIV δύο χιλιάδες εφτακόσια δεκατέσσερα +MMDCCXV δύο χιλιάδες εφτακόσια δεκαπέντε +MMDCCXVI δύο χιλιάδες εφτακόσια δεκαέξι +MMDCCXVII δύο χιλιάδες εφτακόσια δεκαεφτά +MMDCCXVIII δύο χιλιάδες εφτακόσια δεκαοχτώ +MMDCCXIX δύο χιλιάδες εφτακόσια δεκαεννιά +MMDCCXX δύο χιλιάδες εφτακόσια είκοσι +MMDCCXXI δύο χιλιάδες εφτακόσια είκοσι ένα +MMDCCXXII δύο χιλιάδες εφτακόσια είκοσι δύο +MMDCCXXIII δύο χιλιάδες εφτακόσια είκοσι τρία +MMDCCXXIV δύο χιλιάδες εφτακόσια είκοσι τέσσερα +MMDCCXXV δύο χιλιάδες εφτακόσια είκοσι πέντε +MMDCCXXVI δύο χιλιάδες εφτακόσια είκοσι έξι +MMDCCXXVII δύο χιλιάδες εφτακόσια είκοσι εφτά +MMDCCXXVIII δύο χιλιάδες εφτακόσια είκοσι οχτώ +MMDCCXXIX δύο χιλιάδες εφτακόσια είκοσι εννιά +MMDCCXXX δύο χιλιάδες εφτακόσια τριάντα +MMDCCXXXI δύο χιλιάδες εφτακόσια τριάντα ένα +MMDCCXXXII δύο χιλιάδες εφτακόσια τριάντα δύο +MMDCCXXXIII δύο χιλιάδες εφτακόσια τριάντα τρία +MMDCCXXXIV δύο χιλιάδες εφτακόσια τριάντα τέσσερα +MMDCCXXXV δύο χιλιάδες εφτακόσια τριάντα πέντε +MMDCCXXXVI δύο χιλιάδες εφτακόσια τριάντα έξι +MMDCCXXXVII δύο χιλιάδες εφτακόσια τριάντα εφτά +MMDCCXXXVIII δύο χιλιάδες εφτακόσια τριάντα οχτώ +MMDCCXXXIX δύο χιλιάδες εφτακόσια τριάντα εννιά +MMDCCXL δύο χιλιάδες εφτακόσια σαράντα +MMDCCXLI δύο χιλιάδες εφτακόσια σαράντα ένα +MMDCCXLII δύο χιλιάδες εφτακόσια σαράντα δύο +MMDCCXLIII δύο χιλιάδες εφτακόσια σαράντα τρία +MMDCCXLIV δύο χιλιάδες εφτακόσια σαράντα τέσσερα +MMDCCXLV δύο χιλιάδες εφτακόσια σαράντα πέντε +MMDCCXLVI δύο χιλιάδες εφτακόσια σαράντα έξι +MMDCCXLVII δύο χιλιάδες εφτακόσια σαράντα εφτά +MMDCCXLVIII δύο χιλιάδες εφτακόσια σαράντα οχτώ +MMDCCXLIX δύο χιλιάδες εφτακόσια σαράντα εννιά +MMDCCL δύο χιλιάδες εφτακόσια πενήντα +MMDCCLI δύο χιλιάδες εφτακόσια πενήντα ένα +MMDCCLII δύο χιλιάδες εφτακόσια πενήντα δύο +MMDCCLIII δύο χιλιάδες εφτακόσια πενήντα τρία +MMDCCLIV δύο χιλιάδες εφτακόσια πενήντα τέσσερα +MMDCCLV δύο χιλιάδες εφτακόσια πενήντα πέντε +MMDCCLVI δύο χιλιάδες εφτακόσια πενήντα έξι +MMDCCLVII δύο χιλιάδες εφτακόσια πενήντα εφτά +MMDCCLVIII δύο χιλιάδες εφτακόσια πενήντα οχτώ +MMDCCLIX δύο χιλιάδες εφτακόσια πενήντα εννιά +MMDCCLX δύο χιλιάδες εφτακόσια εξήντα +MMDCCLXI δύο χιλιάδες εφτακόσια εξήντα ένα +MMDCCLXII δύο χιλιάδες εφτακόσια εξήντα δύο +MMDCCLXIII δύο χιλιάδες εφτακόσια εξήντα τρία +MMDCCLXIV δύο χιλιάδες εφτακόσια εξήντα τέσσερα +MMDCCLXV δύο χιλιάδες εφτακόσια εξήντα πέντε +MMDCCLXVI δύο χιλιάδες εφτακόσια εξήντα έξι +MMDCCLXVII δύο χιλιάδες εφτακόσια εξήντα εφτά +MMDCCLXVIII δύο χιλιάδες εφτακόσια εξήντα οχτώ +MMDCCLXIX δύο χιλιάδες εφτακόσια εξήντα εννιά +MMDCCLXX δύο χιλιάδες εφτακόσια εβδομήντα +MMDCCLXXI δύο χιλιάδες εφτακόσια εβδομήντα ένα +MMDCCLXXII δύο χιλιάδες εφτακόσια εβδομήντα δύο +MMDCCLXXIII δύο χιλιάδες εφτακόσια εβδομήντα τρία +MMDCCLXXIV δύο χιλιάδες εφτακόσια εβδομήντα τέσσερα +MMDCCLXXV δύο χιλιάδες εφτακόσια εβδομήντα πέντε +MMDCCLXXVI δύο χιλιάδες εφτακόσια εβδομήντα έξι +MMDCCLXXVII δύο χιλιάδες εφτακόσια εβδομήντα εφτά +MMDCCLXXVIII δύο χιλιάδες εφτακόσια εβδομήντα οχτώ +MMDCCLXXIX δύο χιλιάδες εφτακόσια εβδομήντα εννιά +MMDCCLXXX δύο χιλιάδες εφτακόσια ογδόντα +MMDCCLXXXI δύο χιλιάδες εφτακόσια ογδόντα ένα +MMDCCLXXXII δύο χιλιάδες εφτακόσια ογδόντα δύο +MMDCCLXXXIII δύο χιλιάδες εφτακόσια ογδόντα τρία +MMDCCLXXXIV δύο χιλιάδες εφτακόσια ογδόντα τέσσερα +MMDCCLXXXV δύο χιλιάδες εφτακόσια ογδόντα πέντε +MMDCCLXXXVI δύο χιλιάδες εφτακόσια ογδόντα έξι +MMDCCLXXXVII δύο χιλιάδες εφτακόσια ογδόντα εφτά +MMDCCLXXXVIII δύο χιλιάδες εφτακόσια ογδόντα οχτώ +MMDCCLXXXIX δύο χιλιάδες εφτακόσια ογδόντα εννιά +MMDCCXC δύο χιλιάδες εφτακόσια ενενήντα +MMDCCXCI δύο χιλιάδες εφτακόσια ενενήντα ένα +MMDCCXCII δύο χιλιάδες εφτακόσια ενενήντα δύο +MMDCCXCIII δύο χιλιάδες εφτακόσια ενενήντα τρία +MMDCCXCIV δύο χιλιάδες εφτακόσια ενενήντα τέσσερα +MMDCCXCV δύο χιλιάδες εφτακόσια ενενήντα πέντε +MMDCCXCVI δύο χιλιάδες εφτακόσια ενενήντα έξι +MMDCCXCVII δύο χιλιάδες εφτακόσια ενενήντα εφτά +MMDCCXCVIII δύο χιλιάδες εφτακόσια ενενήντα οχτώ +MMDCCXCIX δύο χιλιάδες εφτακόσια ενενήντα εννιά +MMDCCC δύο χιλιάδες οχτακόσια +MMDCCCI δύο χιλιάδες οχτακόσια ένα +MMDCCCII δύο χιλιάδες οχτακόσια δύο +MMDCCCIII δύο χιλιάδες οχτακόσια τρία +MMDCCCIV δύο χιλιάδες οχτακόσια τέσσερα +MMDCCCV δύο χιλιάδες οχτακόσια πέντε +MMDCCCVI δύο χιλιάδες οχτακόσια έξι +MMDCCCVII δύο χιλιάδες οχτακόσια εφτά +MMDCCCVIII δύο χιλιάδες οχτακόσια οχτώ +MMDCCCIX δύο χιλιάδες οχτακόσια εννιά +MMDCCCX δύο χιλιάδες οχτακόσια δέκα +MMDCCCXI δύο χιλιάδες οχτακόσια έντεκα +MMDCCCXII δύο χιλιάδες οχτακόσια δώδεκα +MMDCCCXIII δύο χιλιάδες οχτακόσια δεκατρία +MMDCCCXIV δύο χιλιάδες οχτακόσια δεκατέσσερα +MMDCCCXV δύο χιλιάδες οχτακόσια δεκαπέντε +MMDCCCXVI δύο χιλιάδες οχτακόσια δεκαέξι +MMDCCCXVII δύο χιλιάδες οχτακόσια δεκαεφτά +MMDCCCXVIII δύο χιλιάδες οχτακόσια δεκαοχτώ +MMDCCCXIX δύο χιλιάδες οχτακόσια δεκαεννιά +MMDCCCXX δύο χιλιάδες οχτακόσια είκοσι +MMDCCCXXI δύο χιλιάδες οχτακόσια είκοσι ένα +MMDCCCXXII δύο χιλιάδες οχτακόσια είκοσι δύο +MMDCCCXXIII δύο χιλιάδες οχτακόσια είκοσι τρία +MMDCCCXXIV δύο χιλιάδες οχτακόσια είκοσι τέσσερα +MMDCCCXXV δύο χιλιάδες οχτακόσια είκοσι πέντε +MMDCCCXXVI δύο χιλιάδες οχτακόσια είκοσι έξι +MMDCCCXXVII δύο χιλιάδες οχτακόσια είκοσι εφτά +MMDCCCXXVIII δύο χιλιάδες οχτακόσια είκοσι οχτώ +MMDCCCXXIX δύο χιλιάδες οχτακόσια είκοσι εννιά +MMDCCCXXX δύο χιλιάδες οχτακόσια τριάντα +MMDCCCXXXI δύο χιλιάδες οχτακόσια τριάντα ένα +MMDCCCXXXII δύο χιλιάδες οχτακόσια τριάντα δύο +MMDCCCXXXIII δύο χιλιάδες οχτακόσια τριάντα τρία +MMDCCCXXXIV δύο χιλιάδες οχτακόσια τριάντα τέσσερα +MMDCCCXXXV δύο χιλιάδες οχτακόσια τριάντα πέντε +MMDCCCXXXVI δύο χιλιάδες οχτακόσια τριάντα έξι +MMDCCCXXXVII δύο χιλιάδες οχτακόσια τριάντα εφτά +MMDCCCXXXVIII δύο χιλιάδες οχτακόσια τριάντα οχτώ +MMDCCCXXXIX δύο χιλιάδες οχτακόσια τριάντα εννιά +MMDCCCXL δύο χιλιάδες οχτακόσια σαράντα +MMDCCCXLI δύο χιλιάδες οχτακόσια σαράντα ένα +MMDCCCXLII δύο χιλιάδες οχτακόσια σαράντα δύο +MMDCCCXLIII δύο χιλιάδες οχτακόσια σαράντα τρία +MMDCCCXLIV δύο χιλιάδες οχτακόσια σαράντα τέσσερα +MMDCCCXLV δύο χιλιάδες οχτακόσια σαράντα πέντε +MMDCCCXLVI δύο χιλιάδες οχτακόσια σαράντα έξι +MMDCCCXLVII δύο χιλιάδες οχτακόσια σαράντα εφτά +MMDCCCXLVIII δύο χιλιάδες οχτακόσια σαράντα οχτώ +MMDCCCXLIX δύο χιλιάδες οχτακόσια σαράντα εννιά +MMDCCCL δύο χιλιάδες οχτακόσια πενήντα +MMDCCCLI δύο χιλιάδες οχτακόσια πενήντα ένα +MMDCCCLII δύο χιλιάδες οχτακόσια πενήντα δύο +MMDCCCLIII δύο χιλιάδες οχτακόσια πενήντα τρία +MMDCCCLIV δύο χιλιάδες οχτακόσια πενήντα τέσσερα +MMDCCCLV δύο χιλιάδες οχτακόσια πενήντα πέντε +MMDCCCLVI δύο χιλιάδες οχτακόσια πενήντα έξι +MMDCCCLVII δύο χιλιάδες οχτακόσια πενήντα εφτά +MMDCCCLVIII δύο χιλιάδες οχτακόσια πενήντα οχτώ +MMDCCCLIX δύο χιλιάδες οχτακόσια πενήντα εννιά +MMDCCCLX δύο χιλιάδες οχτακόσια εξήντα +MMDCCCLXI δύο χιλιάδες οχτακόσια εξήντα ένα +MMDCCCLXII δύο χιλιάδες οχτακόσια εξήντα δύο +MMDCCCLXIII δύο χιλιάδες οχτακόσια εξήντα τρία +MMDCCCLXIV δύο χιλιάδες οχτακόσια εξήντα τέσσερα +MMDCCCLXV δύο χιλιάδες οχτακόσια εξήντα πέντε +MMDCCCLXVI δύο χιλιάδες οχτακόσια εξήντα έξι +MMDCCCLXVII δύο χιλιάδες οχτακόσια εξήντα εφτά +MMDCCCLXVIII δύο χιλιάδες οχτακόσια εξήντα οχτώ +MMDCCCLXIX δύο χιλιάδες οχτακόσια εξήντα εννιά +MMDCCCLXX δύο χιλιάδες οχτακόσια εβδομήντα +MMDCCCLXXI δύο χιλιάδες οχτακόσια εβδομήντα ένα +MMDCCCLXXII δύο χιλιάδες οχτακόσια εβδομήντα δύο +MMDCCCLXXIII δύο χιλιάδες οχτακόσια εβδομήντα τρία +MMDCCCLXXIV δύο χιλιάδες οχτακόσια εβδομήντα τέσσερα +MMDCCCLXXV δύο χιλιάδες οχτακόσια εβδομήντα πέντε +MMDCCCLXXVI δύο χιλιάδες οχτακόσια εβδομήντα έξι +MMDCCCLXXVII δύο χιλιάδες οχτακόσια εβδομήντα εφτά +MMDCCCLXXVIII δύο χιλιάδες οχτακόσια εβδομήντα οχτώ +MMDCCCLXXIX δύο χιλιάδες οχτακόσια εβδομήντα εννιά +MMDCCCLXXX δύο χιλιάδες οχτακόσια ογδόντα +MMDCCCLXXXI δύο χιλιάδες οχτακόσια ογδόντα ένα +MMDCCCLXXXII δύο χιλιάδες οχτακόσια ογδόντα δύο +MMDCCCLXXXIII δύο χιλιάδες οχτακόσια ογδόντα τρία +MMDCCCLXXXIV δύο χιλιάδες οχτακόσια ογδόντα τέσσερα +MMDCCCLXXXV δύο χιλιάδες οχτακόσια ογδόντα πέντε +MMDCCCLXXXVI δύο χιλιάδες οχτακόσια ογδόντα έξι +MMDCCCLXXXVII δύο χιλιάδες οχτακόσια ογδόντα εφτά +MMDCCCLXXXVIII δύο χιλιάδες οχτακόσια ογδόντα οχτώ +MMDCCCLXXXIX δύο χιλιάδες οχτακόσια ογδόντα εννιά +MMDCCCXC δύο χιλιάδες οχτακόσια ενενήντα +MMDCCCXCI δύο χιλιάδες οχτακόσια ενενήντα ένα +MMDCCCXCII δύο χιλιάδες οχτακόσια ενενήντα δύο +MMDCCCXCIII δύο χιλιάδες οχτακόσια ενενήντα τρία +MMDCCCXCIV δύο χιλιάδες οχτακόσια ενενήντα τέσσερα +MMDCCCXCV δύο χιλιάδες οχτακόσια ενενήντα πέντε +MMDCCCXCVI δύο χιλιάδες οχτακόσια ενενήντα έξι +MMDCCCXCVII δύο χιλιάδες οχτακόσια ενενήντα εφτά +MMDCCCXCVIII δύο χιλιάδες οχτακόσια ενενήντα οχτώ +MMDCCCXCIX δύο χιλιάδες οχτακόσια ενενήντα εννιά +MMCM δύο χιλιάδες εννιακόσια +MMCMI δύο χιλιάδες εννιακόσια ένα +MMCMII δύο χιλιάδες εννιακόσια δύο +MMCMIII δύο χιλιάδες εννιακόσια τρία +MMCMIV δύο χιλιάδες εννιακόσια τέσσερα +MMCMV δύο χιλιάδες εννιακόσια πέντε +MMCMVI δύο χιλιάδες εννιακόσια έξι +MMCMVII δύο χιλιάδες εννιακόσια εφτά +MMCMVIII δύο χιλιάδες εννιακόσια οχτώ +MMCMIX δύο χιλιάδες εννιακόσια εννιά +MMCMX δύο χιλιάδες εννιακόσια δέκα +MMCMXI δύο χιλιάδες εννιακόσια έντεκα +MMCMXII δύο χιλιάδες εννιακόσια δώδεκα +MMCMXIII δύο χιλιάδες εννιακόσια δεκατρία +MMCMXIV δύο χιλιάδες εννιακόσια δεκατέσσερα +MMCMXV δύο χιλιάδες εννιακόσια δεκαπέντε +MMCMXVI δύο χιλιάδες εννιακόσια δεκαέξι +MMCMXVII δύο χιλιάδες εννιακόσια δεκαεφτά +MMCMXVIII δύο χιλιάδες εννιακόσια δεκαοχτώ +MMCMXIX δύο χιλιάδες εννιακόσια δεκαεννιά +MMCMXX δύο χιλιάδες εννιακόσια είκοσι +MMCMXXI δύο χιλιάδες εννιακόσια είκοσι ένα +MMCMXXII δύο χιλιάδες εννιακόσια είκοσι δύο +MMCMXXIII δύο χιλιάδες εννιακόσια είκοσι τρία +MMCMXXIV δύο χιλιάδες εννιακόσια είκοσι τέσσερα +MMCMXXV δύο χιλιάδες εννιακόσια είκοσι πέντε +MMCMXXVI δύο χιλιάδες εννιακόσια είκοσι έξι +MMCMXXVII δύο χιλιάδες εννιακόσια είκοσι εφτά +MMCMXXVIII δύο χιλιάδες εννιακόσια είκοσι οχτώ +MMCMXXIX δύο χιλιάδες εννιακόσια είκοσι εννιά +MMCMXXX δύο χιλιάδες εννιακόσια τριάντα +MMCMXXXI δύο χιλιάδες εννιακόσια τριάντα ένα +MMCMXXXII δύο χιλιάδες εννιακόσια τριάντα δύο +MMCMXXXIII δύο χιλιάδες εννιακόσια τριάντα τρία +MMCMXXXIV δύο χιλιάδες εννιακόσια τριάντα τέσσερα +MMCMXXXV δύο χιλιάδες εννιακόσια τριάντα πέντε +MMCMXXXVI δύο χιλιάδες εννιακόσια τριάντα έξι +MMCMXXXVII δύο χιλιάδες εννιακόσια τριάντα εφτά +MMCMXXXVIII δύο χιλιάδες εννιακόσια τριάντα οχτώ +MMCMXXXIX δύο χιλιάδες εννιακόσια τριάντα εννιά +MMCMXL δύο χιλιάδες εννιακόσια σαράντα +MMCMXLI δύο χιλιάδες εννιακόσια σαράντα ένα +MMCMXLII δύο χιλιάδες εννιακόσια σαράντα δύο +MMCMXLIII δύο χιλιάδες εννιακόσια σαράντα τρία +MMCMXLIV δύο χιλιάδες εννιακόσια σαράντα τέσσερα +MMCMXLV δύο χιλιάδες εννιακόσια σαράντα πέντε +MMCMXLVI δύο χιλιάδες εννιακόσια σαράντα έξι +MMCMXLVII δύο χιλιάδες εννιακόσια σαράντα εφτά +MMCMXLVIII δύο χιλιάδες εννιακόσια σαράντα οχτώ +MMCMXLIX δύο χιλιάδες εννιακόσια σαράντα εννιά +MMCML δύο χιλιάδες εννιακόσια πενήντα +MMCMLI δύο χιλιάδες εννιακόσια πενήντα ένα +MMCMLII δύο χιλιάδες εννιακόσια πενήντα δύο +MMCMLIII δύο χιλιάδες εννιακόσια πενήντα τρία +MMCMLIV δύο χιλιάδες εννιακόσια πενήντα τέσσερα +MMCMLV δύο χιλιάδες εννιακόσια πενήντα πέντε +MMCMLVI δύο χιλιάδες εννιακόσια πενήντα έξι +MMCMLVII δύο χιλιάδες εννιακόσια πενήντα εφτά +MMCMLVIII δύο χιλιάδες εννιακόσια πενήντα οχτώ +MMCMLIX δύο χιλιάδες εννιακόσια πενήντα εννιά +MMCMLX δύο χιλιάδες εννιακόσια εξήντα +MMCMLXI δύο χιλιάδες εννιακόσια εξήντα ένα +MMCMLXII δύο χιλιάδες εννιακόσια εξήντα δύο +MMCMLXIII δύο χιλιάδες εννιακόσια εξήντα τρία +MMCMLXIV δύο χιλιάδες εννιακόσια εξήντα τέσσερα +MMCMLXV δύο χιλιάδες εννιακόσια εξήντα πέντε +MMCMLXVI δύο χιλιάδες εννιακόσια εξήντα έξι +MMCMLXVII δύο χιλιάδες εννιακόσια εξήντα εφτά +MMCMLXVIII δύο χιλιάδες εννιακόσια εξήντα οχτώ +MMCMLXIX δύο χιλιάδες εννιακόσια εξήντα εννιά +MMCMLXX δύο χιλιάδες εννιακόσια εβδομήντα +MMCMLXXI δύο χιλιάδες εννιακόσια εβδομήντα ένα +MMCMLXXII δύο χιλιάδες εννιακόσια εβδομήντα δύο +MMCMLXXIII δύο χιλιάδες εννιακόσια εβδομήντα τρία +MMCMLXXIV δύο χιλιάδες εννιακόσια εβδομήντα τέσσερα +MMCMLXXV δύο χιλιάδες εννιακόσια εβδομήντα πέντε +MMCMLXXVI δύο χιλιάδες εννιακόσια εβδομήντα έξι +MMCMLXXVII δύο χιλιάδες εννιακόσια εβδομήντα εφτά +MMCMLXXVIII δύο χιλιάδες εννιακόσια εβδομήντα οχτώ +MMCMLXXIX δύο χιλιάδες εννιακόσια εβδομήντα εννιά +MMCMLXXX δύο χιλιάδες εννιακόσια ογδόντα +MMCMLXXXI δύο χιλιάδες εννιακόσια ογδόντα ένα +MMCMLXXXII δύο χιλιάδες εννιακόσια ογδόντα δύο +MMCMLXXXIII δύο χιλιάδες εννιακόσια ογδόντα τρία +MMCMLXXXIV δύο χιλιάδες εννιακόσια ογδόντα τέσσερα +MMCMLXXXV δύο χιλιάδες εννιακόσια ογδόντα πέντε +MMCMLXXXVI δύο χιλιάδες εννιακόσια ογδόντα έξι +MMCMLXXXVII δύο χιλιάδες εννιακόσια ογδόντα εφτά +MMCMLXXXVIII δύο χιλιάδες εννιακόσια ογδόντα οχτώ +MMCMLXXXIX δύο χιλιάδες εννιακόσια ογδόντα εννιά +MMCMXC δύο χιλιάδες εννιακόσια ενενήντα +MMCMXCI δύο χιλιάδες εννιακόσια ενενήντα ένα +MMCMXCII δύο χιλιάδες εννιακόσια ενενήντα δύο +MMCMXCIII δύο χιλιάδες εννιακόσια ενενήντα τρία +MMCMXCIV δύο χιλιάδες εννιακόσια ενενήντα τέσσερα +MMCMXCV δύο χιλιάδες εννιακόσια ενενήντα πέντε +MMCMXCVI δύο χιλιάδες εννιακόσια ενενήντα έξι +MMCMXCVII δύο χιλιάδες εννιακόσια ενενήντα εφτά +MMCMXCVIII δύο χιλιάδες εννιακόσια ενενήντα οχτώ +MMCMXCIX δύο χιλιάδες εννιακόσια ενενήντα εννιά +MMM τρεις χιλιάδες diff --git a/nemo_text_processing/text_normalization/el/data/telephone/__init__.py b/nemo_text_processing/text_normalization/el/data/telephone/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/telephone/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/time/__init__.py b/nemo_text_processing/text_normalization/el/data/time/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/time/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/data/whitelist.tsv b/nemo_text_processing/text_normalization/el/data/whitelist.tsv new file mode 100644 index 000000000..a56f92d78 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/whitelist.tsv @@ -0,0 +1,17 @@ +κ.λπ. και τα λοιπά +κ.τ.λ. και τα λοιπά +κ.ά. και άλλα +κ.ο.κ. και ούτω καθεξής +π.χ. παραδείγματος χάριν +δηλ. δηλαδή +βλ. βλέπε +π.Χ. προ Χριστού +μ.Χ. μετά Χριστόν +μ.μ. μετά μεσημβρίας +π.μ. προ μεσημβρίας +τηλ. τηλέφωνο +αρ. αριθμός +σελ. σελίδα +κ. κύριος +κα κυρία +Δρ. δόκτωρ diff --git a/nemo_text_processing/text_normalization/el/data/whitelist/symbol.tsv b/nemo_text_processing/text_normalization/el/data/whitelist/symbol.tsv new file mode 100644 index 000000000..8f8a1b28b --- /dev/null +++ b/nemo_text_processing/text_normalization/el/data/whitelist/symbol.tsv @@ -0,0 +1,20 @@ +& και +# hash +@ στο +§ παράγραφος +™ εμπορικό σήμα +® κατατεθέν σήμα +© πνευματική ιδιοκτησία +_ κάτω παύλα +% τοις εκατό +* αστερίσκος ++ συν +/ κάθετος += ίσον +^ περισπωμένη +| κάθετη γραμμή +~ περίπου +$ δολάριο +£ λίρα +€ ευρώ +° βαθμός diff --git a/nemo_text_processing/text_normalization/el/graph_utils.py b/nemo_text_processing/text_normalization/el/graph_utils.py new file mode 100644 index 000000000..76d5557ad --- /dev/null +++ b/nemo_text_processing/text_normalization/el/graph_utils.py @@ -0,0 +1,86 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright 2015 and onwards Google, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_SIGMA, NEMO_SPACE + +# Greek numerals inflect for grammatical gender. The cardinal/ordinal grammars are +# built in the NEUTER citation form (the form used when counting, e.g. "τρία", "τέσσερα", +# "διακόσια"), which is the correct default for a standalone number. The helpers below +# rewrite a rendered neuter number string into its masculine or feminine equivalent so +# that consumers (measure, money, date, thousands multiplier) can enforce agreement. +# +# Only the units 1/3/4, the teens 13/14 and the hundreds series 200-900 have distinct +# gendered forms; 2 (δύο) and everything else are invariable. The rewrites are applied at +# word boundaries (start/end of string, spaces, or a closing quote) so that, e.g., +# "τρία" -> "τρεις" fires as a full word but the substring inside "δεκατρία" does not. + +# Contexts marking a full-word boundary within a number string. +_LEFT = pynini.union("[BOS]", NEMO_SPACE) +_RIGHT = pynini.union("[EOS]", NEMO_SPACE, '"') + +# Neuter -> feminine (μία, τρεις, τέσσερις, ...αντες, διακόσιες, ...). +_NEUTER_TO_FEM = pynini.string_map( + [ + ("ένα", "μία"), + ("τρία", "τρεις"), + ("τέσσερα", "τέσσερις"), + ("δεκατρία", "δεκατρείς"), + ("δεκατέσσερα", "δεκατέσσερις"), + ("διακόσια", "διακόσιες"), + ("τριακόσια", "τριακόσιες"), + ("τετρακόσια", "τετρακόσιες"), + ("πεντακόσια", "πεντακόσιες"), + ("εξακόσια", "εξακόσιες"), + ("εφτακόσια", "εφτακόσιες"), + ("οχτακόσια", "οχτακόσιες"), + ("εννιακόσια", "εννιακόσιες"), + ] +) + +# Neuter -> masculine (ένας, τρεις, τέσσερις, διακόσιοι, ...). +_NEUTER_TO_MASC = pynini.string_map( + [ + ("ένα", "ένας"), + ("τρία", "τρεις"), + ("τέσσερα", "τέσσερις"), + ("δεκατρία", "δεκατρείς"), + ("δεκατέσσερα", "δεκατέσσερις"), + ("διακόσια", "διακόσιοι"), + ("τριακόσια", "τριακόσιοι"), + ("τετρακόσια", "τετρακόσιοι"), + ("πεντακόσια", "πεντακόσιοι"), + ("εξακόσια", "εξακόσιοι"), + ("εφτακόσια", "εφτακόσιοι"), + ("οχτακόσια", "οχτακόσιοι"), + ("εννιακόσια", "εννιακόσιοι"), + ] +) + +_FEM_REWRITE = pynini.cdrewrite(_NEUTER_TO_FEM, _LEFT, _RIGHT, NEMO_SIGMA).optimize() +_MASC_REWRITE = pynini.cdrewrite(_NEUTER_TO_MASC, _LEFT, _RIGHT, NEMO_SIGMA).optimize() + + +def shift_cardinal_gender_fem(fst: "pynini.FstLike") -> "pynini.FstLike": + """Rewrites the (neuter) output of ``fst`` into its feminine form, e.g. + "τρία" -> "τρεις", "διακόσια είκοσι τρία" -> "διακόσιες είκοσι τρεις".""" + return fst @ _FEM_REWRITE + + +def shift_cardinal_gender_masc(fst: "pynini.FstLike") -> "pynini.FstLike": + """Rewrites the (neuter) output of ``fst`` into its masculine form, e.g. + "τρία" -> "τρεις", "διακόσια" -> "διακόσιοι", "ένα" -> "ένας".""" + return fst @ _MASC_REWRITE diff --git a/nemo_text_processing/text_normalization/el/taggers/__init__.py b/nemo_text_processing/text_normalization/el/taggers/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/taggers/cardinal.py b/nemo_text_processing/text_normalization/el/taggers/cardinal.py new file mode 100644 index 000000000..4a3cb26eb --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/cardinal.py @@ -0,0 +1,140 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.graph_utils import shift_cardinal_gender_fem +from nemo_text_processing.text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_DIGIT, GraphFst, insert_space + + +class CardinalFst(GraphFst): + """ + Finite state transducer for classifying cardinals in Greek, e.g. + 55 -> cardinal { integer: "πενήντα πέντε" } + 123 -> cardinal { integer: "εκατόν είκοσι τρία" } + + Numbers are rendered in the neuter citation form. Supports 0 up to 999999999999 + (i.e. up to hundreds of billions). Greek numerals inflect for gender; the thousands + multiplier is feminine ("τρεις χιλιάδες"), while the units block and the + million/billion multipliers stay neuter. + + Args: + deterministic: if True will provide a single transduction option, + for False multiple options (used for audio-based normalization) + """ + + def __init__(self, deterministic: bool = True): + super().__init__(name="cardinal", kind="classify", deterministic=deterministic) + + graph_digit = pynini.string_file(get_abs_path("data/numbers/digit.tsv")) # 1-9 -> word + graph_teen = pynini.string_file(get_abs_path("data/numbers/teen.tsv")) # 10-19 -> word + graph_ties = pynini.string_file(get_abs_path("data/numbers/ties.tsv")) # 2-9 -> tens word + graph_hundreds_map = pynini.string_file(get_abs_path("data/numbers/hundreds.tsv")) # 2-9 -> hundreds word + + graph_digit_no_one = (NEMO_DIGIT - "1") @ graph_digit + + self.zero = pynini.cross("0", "μηδέν") + + # single_digits_graph: maps digit characters → Greek words with spaces between + # e.g., "123" → "ένα δύο τρία" + single_digits_graph = graph_digit | self.zero + self.single_digits_graph = single_digits_graph + pynini.closure(insert_space + single_digits_graph) + + # --- tens: 10-99 --- + tens_exact = graph_ties + pynutil.delete("0") # 20, 30, ... 90 + tens_unit = graph_ties + insert_space + graph_digit # 21-99 with non-zero unit + graph_tens = graph_teen | tens_exact | tens_unit + self.graph_tens = graph_tens.optimize() + + # remainder for the last two digits of a hundreds group (01-99, non-zero) + two_digit_non_zero = (pynutil.delete("0") + insert_space + graph_digit) | (insert_space + graph_tens) + + # --- hundreds: 100-999 --- + hundred_one_exact = pynini.cross("100", "εκατό") + hundred_one_rem = pynini.cross("1", "εκατόν") + two_digit_non_zero # 101-199 + hundreds_exact = graph_hundreds_map + pynutil.delete("00") # 200, 300, ... 900 + hundreds_rem = graph_hundreds_map + two_digit_non_zero # 201-999 + graph_hundreds = hundred_one_exact | hundred_one_rem | hundreds_exact | hundreds_rem + self.graph_hundreds = graph_hundreds.optimize() + + # numbers 1-999 (neuter, variable length, no leading zeros) + graph_one_to_999 = graph_digit | graph_tens | graph_hundreds + self.graph_one_to_999 = graph_one_to_999.optimize() + + # multiplier for the thousands group (2-999); the standalone "1" is handled as "χίλια" + multiplier_base = graph_digit_no_one | graph_tens | graph_hundreds + fem_multiplier = shift_cardinal_gender_fem(multiplier_base) + + # --- thousands: 1000-999999 (variable length, no leading zeros) --- + thousand_one = pynini.cross("1", "χίλια") + thousands_head = thousand_one | (fem_multiplier + insert_space + pynutil.insert("χιλιάδες")) + rem3 = ( + pynutil.delete("000") + | (pynutil.delete("00") + insert_space + graph_digit) + | (pynutil.delete("0") + insert_space + graph_tens) + | (insert_space + graph_hundreds) + ) + graph_thousands = thousands_head + rem3 + self.graph_thousands = graph_thousands.optimize() + + # --- fixed-width 3-digit group blocks, used for the remainders of large numbers --- + # a non-zero group of exactly three digits (001-999), neuter + group_nonzero = (pynutil.delete("00") + graph_digit) | (pynutil.delete("0") + graph_tens) | graph_hundreds + # same but excluding 001 (used where "one" needs a scale word instead) + group_nonzero_no_one = ( + (pynutil.delete("00") + graph_digit_no_one) | (pynutil.delete("0") + graph_tens) | graph_hundreds + ) + + # each *_group_sp consumes exactly three digits and emits a leading space when non-zero + units_group_sp = pynutil.delete("000") | (insert_space + group_nonzero) + thousands_group_sp = ( + pynutil.delete("000") + | (insert_space + pynini.cross("001", "χίλια")) + | ( + insert_space + + shift_cardinal_gender_fem(group_nonzero_no_one) + + insert_space + + pynutil.insert("χιλιάδες") + ) + ) + millions_group_sp = ( + pynutil.delete("000") + | (insert_space + pynini.cross("001", "ένα εκατομμύριο")) + | (insert_space + group_nonzero_no_one + insert_space + pynutil.insert("εκατομμύρια")) + ) + + # --- millions: 1000000 - 999999999 (7-9 digits) --- + million_one = pynini.cross("1", "ένα") + insert_space + pynutil.insert("εκατομμύριο") + million_multi = multiplier_base + insert_space + pynutil.insert("εκατομμύρια") + million_head = million_one | million_multi + rem6 = thousands_group_sp + units_group_sp + graph_millions = million_head + rem6 + self.graph_millions = graph_millions.optimize() + + # --- billions: 1000000000 - 999999999999 (10-12 digits) --- + billion_one = pynini.cross("1", "ένα") + insert_space + pynutil.insert("δισεκατομμύριο") + billion_multi = multiplier_base + insert_space + pynutil.insert("δισεκατομμύρια") + billion_head = billion_one | billion_multi + rem9 = millions_group_sp + thousands_group_sp + units_group_sp + graph_billions = billion_head + rem9 + self.graph_billions = graph_billions.optimize() + + graph = self.zero | graph_one_to_999 | graph_thousands | graph_millions | graph_billions + self.graph_no_tokens = graph.optimize() + + final_graph = pynutil.insert("integer: \"") + self.graph_no_tokens + pynutil.insert("\"") + self.final_graph = final_graph + self.fst = self.add_tokens(final_graph).optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/date.py b/nemo_text_processing/text_normalization/el/taggers/date.py new file mode 100644 index 000000000..7a2148271 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/date.py @@ -0,0 +1,48 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import ( + NEMO_DIGIT, + NEMO_NOT_QUOTE, + GraphFst, + delete_extra_space, + delete_space, + insert_space, +) + + +class DateFst(GraphFst): + def __init__(self, cardinal: GraphFst, deterministic: bool = True): + super().__init__(name="date", kind="classify", deterministic=deterministic) + + cardinal_graph = cardinal.graph_no_tokens + month_map = pynini.string_file(get_abs_path("data/dates/months.tsv")).optimize() + + delete_sep = pynini.cross(pynini.union("/", "-", "."), " ") + + zero = pynutil.add_weight(pynutil.delete("0"), -0.1) + digit_to_cardinal = pynini.compose(NEMO_DIGIT, cardinal_graph) + + day = zero + digit_to_cardinal + day |= pynini.compose( + (pynini.union("1", "2", "3") + NEMO_DIGIT) | NEMO_DIGIT, + cardinal_graph, + ) + day = pynutil.insert("day: \"") + day + pynutil.insert("\"") + + month_lookup = (pynini.accep("0") + NEMO_DIGIT) | ("1" + NEMO_DIGIT) + month_name = pynini.compose(month_lookup, month_map) + month_num = zero + digit_to_cardinal + month_num |= pynini.compose(pynini.accep("1") + NEMO_DIGIT, cardinal_graph) + # Prefer the spelled-out month name; fall back to a numeric reading only for + # out-of-range values (13-19) that have no entry in months.tsv. The weight must + # beat the -0.1 leading-zero bonus inside month_num so 01/03/04/07 don't tie. + month = pynutil.insert("month: \"") + (month_name | pynutil.add_weight(month_num, 1.0)) + pynutil.insert("\"") + + year = pynini.compose(NEMO_DIGIT**4, cardinal_graph).optimize() + year = pynutil.insert("year: \"") + year + pynutil.insert("\"") + year_optional = pynini.closure(delete_sep + year, 0, 1) + + graph = (day + delete_sep + month + year_optional) | year + self.fst = self.add_tokens(graph).optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/decimal.py b/nemo_text_processing/text_normalization/el/taggers/decimal.py new file mode 100644 index 000000000..938b808b3 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/decimal.py @@ -0,0 +1,53 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import GraphFst, insert_space + + +class DecimalFst(GraphFst): + """ + Finite state transducer for classifying decimals in Greek, e.g. + 3,5 -> decimal { integer_part: "τρία" fractional_part: "πέντε" } + -0,05 -> decimal { negative: "true" integer_part: "μηδέν" fractional_part: "μηδέν πέντε" } + + The decimal separator is the comma. The integer part is rendered as a cardinal (neuter), + while the fractional part is read digit by digit so that leading zeros are preserved. + + Args: + cardinal: CardinalFst + deterministic: if True will provide a single transduction option, + for False multiple options (used for audio-based normalization) + """ + + def __init__(self, cardinal: GraphFst, deterministic: bool = True): + super().__init__(name="decimal", kind="classify", deterministic=deterministic) + + cardinal_graph = cardinal.graph_no_tokens + + graph_digit = pynini.string_file(get_abs_path("data/numbers/digit.tsv")) + digit_all = graph_digit | pynini.cross("0", "μηδέν") + fractional = digit_all + pynini.closure(insert_space + digit_all) + + optional_negative = pynini.closure(pynutil.insert("negative: ") + pynini.cross("-", "\"true\" "), 0, 1) + + graph_integer = pynutil.insert("integer_part: \"") + cardinal_graph + pynutil.insert("\"") + graph_fractional = pynutil.insert("fractional_part: \"") + fractional + pynutil.insert("\"") + + final_graph = optional_negative + graph_integer + pynutil.delete(",") + insert_space + graph_fractional + self.final_graph = final_graph + self.fst = self.add_tokens(final_graph).optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/electronic.py b/nemo_text_processing/text_normalization/el/taggers/electronic.py new file mode 100644 index 000000000..f921b81d6 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/electronic.py @@ -0,0 +1,68 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import ( + NEMO_ALPHA, + NEMO_DIGIT, + NEMO_NOT_QUOTE, + GraphFst, + delete_space, + insert_space, +) + + +class ElectronicFst(GraphFst): + def __init__(self, deterministic: bool = True): + super().__init__(name="electronic", kind="classify", deterministic=deterministic) + + accepted_symbols = pynini.union("-", "_", "+", "~", ".") + + username = ( + pynutil.insert("username: \"") + + (NEMO_ALPHA | NEMO_DIGIT | accepted_symbols) + + pynini.closure(NEMO_ALPHA | NEMO_DIGIT | accepted_symbols) + + pynutil.insert("\"") + + pynutil.delete("@") + ) + + domain_graph = ( + (NEMO_ALPHA | NEMO_DIGIT) + + pynini.closure(NEMO_ALPHA | NEMO_DIGIT | pynini.accep("-") | pynini.accep(".")) + + (NEMO_ALPHA | NEMO_DIGIT) + ) + domain_graph = pynutil.insert("domain: \"") + domain_graph + pynutil.insert("\"") + tagger_graph = (username + domain_graph).optimize() + + user_name = ( + pynutil.delete("username:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(pynutil.add_weight(NEMO_NOT_QUOTE + insert_space, 1.1)) + + pynutil.delete("\"") + ) + + domain_default = ( + pynini.closure(NEMO_NOT_QUOTE + insert_space) + + pynini.cross(".", "τελεία ") + + NEMO_NOT_QUOTE + + pynini.closure(insert_space + NEMO_NOT_QUOTE) + ) + + server_default = pynini.closure((NEMO_ALPHA | NEMO_DIGIT) + insert_space, 1) + + domain = ( + pynutil.delete("domain:") + + delete_space + + pynutil.delete("\"") + + (pynutil.add_weight(server_default, 1.1)) + + (pynutil.add_weight(domain_default, 1.1)) + + delete_space + + pynutil.delete("\"") + ) + + graph = user_name + delete_space + pynutil.insert("στο ") + delete_space + domain + delete_space + verbalizer_graph = graph.optimize() + + self.final_graph = (tagger_graph @ verbalizer_graph).optimize() + self.fst = pynutil.insert("username: \"") + self.final_graph + pynutil.insert("\"") + self.fst = self.add_tokens(self.fst).optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/fraction.py b/nemo_text_processing/text_normalization/el/taggers/fraction.py new file mode 100644 index 000000000..cef07b9f5 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/fraction.py @@ -0,0 +1,69 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.taggers.ordinal import OrdinalFst +from nemo_text_processing.text_normalization.en.graph_utils import GraphFst + + +class FractionFst(GraphFst): + """ + Finite state transducer for classifying fractions in Greek, e.g. + 1/2 -> fraction { numerator: "ένα" denominator: "δεύτερο" } + 3/4 -> fraction { numerator: "τρία" denominator: "τέταρτα" } + + The numerator is a neuter cardinal and the denominator a neuter ordinal, singular when the + numerator is one ("ένα τρίτο") and plural otherwise ("δύο τρίτα"). + + Args: + cardinal: CardinalFst + ordinal: OrdinalFst + deterministic: if True will provide a single transduction option, + for False multiple options (used for audio-based normalization) + """ + + def __init__(self, cardinal: GraphFst, ordinal: GraphFst = None, deterministic: bool = True): + super().__init__(name="fraction", kind="classify", deterministic=deterministic) + + if ordinal is None: + ordinal = OrdinalFst(cardinal=cardinal, deterministic=deterministic) + + num_graph = cardinal.graph_no_tokens + num_no_one = pynini.difference(pynini.project(num_graph, "input"), pynini.accep("1")) @ num_graph + + denom_sg = ordinal.graph_neuter_sg + denom_pl = ordinal.graph_neuter_pl + + optional_negative = pynini.closure(pynutil.insert("negative: ") + pynini.cross("-", "\"true\" "), 0, 1) + + numerator_one = pynutil.insert("numerator: \"") + pynini.cross("1", "ένα") + pynutil.insert("\"") + numerator_multi = pynutil.insert("numerator: \"") + num_no_one + pynutil.insert("\"") + + branch_one = ( + numerator_one + pynutil.delete("/") + pynutil.insert(" denominator: \"") + denom_sg + pynutil.insert("\"") + ) + branch_multi = ( + numerator_multi + + pynutil.delete("/") + + pynutil.insert(" denominator: \"") + + denom_pl + + pynutil.insert("\"") + ) + + graph = branch_one | branch_multi + final_graph = optional_negative + graph + self.final_graph = final_graph + self.fst = self.add_tokens(final_graph).optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/measure.py b/nemo_text_processing/text_normalization/el/taggers/measure.py new file mode 100644 index 000000000..d6f3646d7 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/measure.py @@ -0,0 +1,171 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.utils import get_abs_path, load_labels +from nemo_text_processing.text_normalization.en.graph_utils import ( + NEMO_ALPHA, + NEMO_DIGIT, + NEMO_NOT_QUOTE, + NEMO_SPACE, + GraphFst, + delete_space, + insert_space, +) + +GREEK_LOWER = pynini.union( + "α", + "β", + "γ", + "δ", + "ε", + "ζ", + "η", + "θ", + "ι", + "κ", + "λ", + "μ", + "ν", + "ξ", + "ο", + "π", + "ρ", + "σ", + "ς", + "τ", + "υ", + "φ", + "χ", + "ψ", + "ω", +) +GREEK_UPPER = pynini.union( + "Α", + "Β", + "Γ", + "Δ", + "Ε", + "Ζ", + "Η", + "Θ", + "Ι", + "Κ", + "Λ", + "Μ", + "Ν", + "Ξ", + "Ο", + "Π", + "Ρ", + "Σ", + "Τ", + "Υ", + "Φ", + "Χ", + "Ψ", + "Ω", +) +GREEK_ALPHA = pynini.union(GREEK_LOWER, GREEK_UPPER) + + +class MeasureFst(GraphFst): + def __init__(self, cardinal: GraphFst, decimal: GraphFst = None, deterministic: bool = True): + super().__init__(name="measure", kind="classify", deterministic=deterministic) + + cardinal_graph = cardinal.graph_no_tokens + graph_unit = pynini.string_file(get_abs_path("data/measure/units.tsv")).optimize() + + delete_opt_space = pynini.closure(pynutil.delete(" "), 0, 1) + + cardinal_space = pynutil.insert("integer: \"") + cardinal_graph + pynutil.insert("\"") + + default_units = pynutil.insert("units: \"") + graph_unit + pynutil.insert("\"") + + tagger_graph = cardinal_space + delete_opt_space + default_units + + integer = pynutil.delete("integer: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + unit = pynutil.delete("units: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + + verbalizer_graph = integer + delete_space + insert_space + unit + + self.final_graph = (tagger_graph @ verbalizer_graph).optimize() + + graph_standard = pynutil.insert("integer: \"") + self.final_graph + pynutil.insert("\"") + + math_operations = pynini.string_file(get_abs_path("data/measure/math_operation.tsv")) + delimiter = pynini.accep(" ") | pynutil.insert(" ") + + math = ( + (cardinal_graph | NEMO_ALPHA) + + delimiter + + math_operations + + (delimiter | NEMO_ALPHA) + + cardinal_graph + + delimiter + + pynini.cross("=", "ίσον") + + delimiter + + (cardinal_graph | NEMO_ALPHA) + ) + + math |= ( + (cardinal_graph | NEMO_ALPHA) + + delimiter + + pynini.cross("=", "ίσον") + + delimiter + + (cardinal_graph | NEMO_ALPHA) + + delimiter + + math_operations + + delimiter + + cardinal_graph + ) + + math = ( + pynutil.insert('units: "math" cardinal { integer: "') + math + pynutil.insert('" } preserve_order: true') + ) + + address = self.get_address_graph(cardinal) + address = pynutil.insert('units: "address" integer: "') + address + pynutil.insert('" preserve_order: true') + + graph = graph_standard | math | address + self.fst = self.add_tokens(graph).optimize() + + def get_address_graph(self, cardinal): + address_num = pynini.compose(NEMO_DIGIT ** (1, 2), cardinal.graph_no_tokens) + address_num |= address_num + insert_space + pynini.compose(NEMO_DIGIT ** (3, 4), cardinal.graph_no_tokens) + + direction = ( + pynini.cross("β", "Βόρεια") + | pynini.cross("Β", "Βόρεια") + | pynini.cross("ν", "Νότια") + | pynini.cross("Ν", "Νότια") + | pynini.cross("α", "Ανατολική") + | pynini.cross("Α", "Ανατολική") + | pynini.cross("δ", "Δυτική") + | pynini.cross("Δ", "Δυτική") + ) + pynini.closure(pynutil.delete("."), 0, 1) + direction = pynini.closure(pynini.accep(NEMO_SPACE) + direction, 0, 1) + + address_words = pynini.string_file(get_abs_path("data/address/address_word.tsv")) + address_words = ( + pynini.accep(NEMO_SPACE) + + address_words + + NEMO_SPACE + + pynini.closure(GREEK_ALPHA, 1) + + pynini.closure(NEMO_SPACE + pynini.closure(GREEK_ALPHA, 1)) + ) + + comma_space = pynutil.delete(",") + pynini.accep(NEMO_SPACE) + + city = pynini.closure(GREEK_ALPHA | pynini.accep(NEMO_SPACE), 1) + city = pynini.closure(comma_space + city, 0, 1) + + states = load_labels(get_abs_path("data/address/state.tsv")) + state_graph = pynini.string_map(states) + state = pynini.invert(state_graph) + state = pynini.closure(comma_space + state, 0, 1) + + zip_code = pynini.compose(NEMO_DIGIT**5, cardinal.single_digits_graph) + zip_code = pynini.closure(comma_space + zip_code, 0, 1) + + address = address_num + direction + address_words + pynini.closure(city + state + zip_code, 0, 1) + return address diff --git a/nemo_text_processing/text_normalization/el/taggers/money.py b/nemo_text_processing/text_normalization/el/taggers/money.py new file mode 100644 index 000000000..6c22191c7 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/money.py @@ -0,0 +1,93 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.graph_utils import shift_cardinal_gender_fem +from nemo_text_processing.text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import GraphFst, insert_space + +# Each currency: symbols/codes that denote it, its grammatical gender, the singular and plural +# major-unit nouns, and the singular and plural minor-unit (cents) nouns. The amount agrees in +# gender with the currency, so e.g. λίρα (feminine) yields "τρεις λίρες" not "τρία λίρες". +_CURRENCIES = [ + (["€", "EUR", "ευρώ"], "n", "ευρώ", "ευρώ", "λεπτό", "λεπτά"), + (["$", "USD"], "n", "δολάριο", "δολάρια", "σεντ", "σεντ"), + (["£", "GBP"], "f", "λίρα", "λίρες", "πένα", "πένες"), +] + + +class MoneyFst(GraphFst): + """ + Finite state transducer for classifying money in Greek, e.g. + €5 -> money { integer_part: "πέντε ευρώ" } + €3,50 -> money { integer_part: "τρία ευρώ" fractional_part: "πενήντα λεπτά" } + £1 -> money { integer_part: "μία λίρα" } + + The currency symbol may precede or follow the amount. The amount agrees in gender with the + currency and the noun is singular when the amount is one. + + Args: + cardinal: CardinalFst + decimal: DecimalFst (unused for now, kept for interface symmetry) + deterministic: if True will provide a single transduction option, + for False multiple options (used for audio-based normalization) + """ + + def __init__(self, cardinal: GraphFst, decimal: GraphFst = None, deterministic: bool = True): + super().__init__(name="money", kind="classify", deterministic=deterministic) + + graph_digit = pynini.string_file(get_abs_path("data/numbers/digit.tsv")) + base_amount = cardinal.graph_no_tokens + amount_no_one = pynini.difference(pynini.project(base_amount, "input"), pynini.accep("1")) @ base_amount + + # cents amount: exactly two digits, 01-99, neuter + cents_one = pynini.cross("01", "ένα") + cents_multi = (pynutil.delete("0") + graph_digit) | cardinal.graph_tens + cents_multi = pynini.difference(pynini.project(cents_multi, "input"), pynini.accep("01")) @ cents_multi + + money_graph = None + for symbols, gender, major_sg, major_pl, minor_sg, minor_pl in _CURRENCIES: + symbol = pynini.union(*symbols) + if gender == "f": + amount_multi = shift_cardinal_gender_fem(amount_no_one) + one_word = "μία" + else: + amount_multi = amount_no_one + one_word = "ένα" + + major_one = pynini.cross("1", one_word) + insert_space + pynutil.insert(major_sg) + major_many = amount_multi + insert_space + pynutil.insert(major_pl) + major = major_one | major_many + + cent_one = cents_one + insert_space + pynutil.insert(minor_sg) + cent_many = cents_multi + insert_space + pynutil.insert(minor_pl) + cents = cent_one | cent_many + + integer_field = pynutil.insert("integer_part: \"") + major + pynutil.insert("\"") + cents_field = pynutil.insert(" fractional_part: \"") + cents + pynutil.insert("\"") + amount_with_cents = integer_field + pynutil.delete(",") + cents_field + amount = amount_with_cents | integer_field + + # symbol/code before the amount (with optional space) or after it (with optional space) + delete_opt_space = pynini.closure(pynutil.delete(" "), 0, 1) + before = pynutil.delete(symbol) + delete_opt_space + amount + after = amount + delete_opt_space + pynutil.delete(symbol) + currency_graph = before | after + + money_graph = currency_graph if money_graph is None else (money_graph | currency_graph) + + self.final_graph = money_graph + self.fst = self.add_tokens(money_graph).optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/ordinal.py b/nemo_text_processing/text_normalization/el/taggers/ordinal.py new file mode 100644 index 000000000..4cc05939d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/ordinal.py @@ -0,0 +1,106 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_SIGMA, GraphFst, insert_space + +# Word-final boundary for the ending rewrites (space between compound components, or end of string). +_WORD_END = pynini.union(" ", "[EOS]") + +# Greek ordinals are written as a digit followed by a gendered/case ending, e.g. "1ος", "1η", +# "1ο", "1ου", "21ές". We build the masculine-nominative form once ("εικοστός πρώτος") and rewrite +# every component's ending to the target gender/case. The masculine base ends either in an +# unaccented "ος" (accent on the stem: πρώτος, δεύτερος, ...) or an accented "ός" (accent on the +# ending: εικοστός, εκατοστός, ...); both patterns are handled in parallel so the accent lands +# correctly (πρώτος -> πρώτη but εικοστός -> εικοστή). +# +# (written ending, (target for stem-accented "ος", target for ending-accented "ός")) or None for +# the identity (masculine nominative) case. +_ENDINGS = [ + ("ος", None), # masculine nominative singular + ("η", ("η", "ή")), # feminine nominative singular + ("ο", ("ο", "ό")), # neuter nom/acc singular, masculine accusative singular + ("ον", ("ο", "ό")), # masculine accusative singular (archaic -ον) + ("ου", ("ου", "ού")), # genitive singular (masculine/neuter) + ("ης", ("ης", "ής")), # feminine genitive singular + ("οι", ("οι", "οί")), # masculine nominative plural + ("ες", ("ες", "ές")), # feminine nominative/accusative plural + ("α", ("α", "ά")), # neuter nominative/accusative plural + ("ων", ("ων", "ών")), # genitive plural (all genders) + ("ους", ("ους", "ούς")), # masculine accusative plural +] + + +def _ending_rewrite(unaccented: str, accented: str) -> "pynini.FstLike": + mapping = pynini.string_map([("ος", unaccented), ("ός", accented)]) + return pynini.cdrewrite(mapping, "", _WORD_END, NEMO_SIGMA) + + +class OrdinalFst(GraphFst): + """ + Finite state transducer for classifying ordinals in Greek, e.g. + 1ος -> ordinal { integer: "πρώτος" } + 21η -> ordinal { integer: "εικοστή πρώτη" } + 3ου -> ordinal { integer: "τρίτου" } + + Supports ordinals 1-999 in every gender/case (nominative, genitive, accusative; + singular and plural). + + Args: + cardinal: CardinalFst (unused for now, kept for interface symmetry) + deterministic: if True will provide a single transduction option, + for False multiple options (used for audio-based normalization) + """ + + def __init__(self, cardinal: GraphFst = None, deterministic: bool = True): + super().__init__(name="ordinal", kind="classify", deterministic=deterministic) + + ord_digit = pynini.string_file(get_abs_path("data/ordinal/digit.tsv")) # 1-9 + ord_teen = pynini.string_file(get_abs_path("data/ordinal/teen.tsv")) # 10-19 + ord_ties = pynini.string_file(get_abs_path("data/ordinal/ties.tsv")) # 2-9 -> tens ordinal + ord_hundreds = pynini.string_file(get_abs_path("data/ordinal/hundreds.tsv")) # 1-9 -> hundreds ordinal + + # tens 10-99 (masculine nominative) + tens_exact = ord_ties + pynutil.delete("0") + tens_compound = ord_ties + insert_space + ord_digit + graph_tens = ord_teen | tens_exact | tens_compound + + two_digit_rem = (pynutil.delete("0") + insert_space + ord_digit) | (insert_space + graph_tens) + hundreds_exact = ord_hundreds + pynutil.delete("00") + hundreds_rem = ord_hundreds + two_digit_rem + graph_hundreds = hundreds_exact | hundreds_rem + + number_to_masc = (ord_digit | graph_tens | graph_hundreds).optimize() + self.number_to_masc = number_to_masc + # neuter ordinal forms, exposed for the fraction grammar (denominators) + self.graph_neuter_sg = (number_to_masc @ _ending_rewrite("ο", "ό")).optimize() + self.graph_neuter_pl = (number_to_masc @ _ending_rewrite("α", "ά")).optimize() + + graph = None + for suffix, target in _ENDINGS: + if target is None: + inflected = number_to_masc + else: + inflected = number_to_masc @ _ending_rewrite(target[0], target[1]) + path = inflected + pynutil.delete(suffix) + graph = path if graph is None else (graph | path) + + self.graph = graph.optimize() + + final_graph = pynutil.insert("integer: \"") + self.graph + pynutil.insert("\"") + self.final_graph = final_graph + self.fst = self.add_tokens(final_graph).optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/punctuation.py b/nemo_text_processing/text_normalization/el/taggers/punctuation.py new file mode 100644 index 000000000..06e72f409 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/punctuation.py @@ -0,0 +1,38 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import GraphFst + + +class PunctuationFst(GraphFst): + """ + Finite state transducer for classifying punctuation, e.g. + , -> tokens { name: "," } + + Includes Greek-specific marks: the ano teleia "·" and the Greek quotation marks «». + Note the Greek question mark is the Latin semicolon ";" which is already covered. + """ + + def __init__(self): + super().__init__(name="punctuation", kind="classify") + + s = "!#$%&\'()*+,-./:;<=>?@^_`{|}~" + greek = "·«»…–—΄" + punct = pynini.union(*s) | pynini.union(*greek) + + graph = pynutil.insert("name: \"") + punct + pynutil.insert("\"") + self.fst = graph.optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/range.py b/nemo_text_processing/text_normalization/el/taggers/range.py new file mode 100644 index 000000000..6b3e0bc2a --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/range.py @@ -0,0 +1,145 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_DIGIT, GraphFst, convert_space + + +class RangeFst(GraphFst): + """ + Composite class for range/quantity expressions in Greek text normalization. + + Handles: + - Time ranges: "5:00-6:00" → "5:00 προς 6:00" + - Year ranges: "1980-1985" → "1980 προς 1985" + - Mid-year: "mid-1980" → "μέσα 1980" + - Number ranges: "100-200" → "100 προς 200" + - Plus: "100+" → "100 συν" + - Approx: "~100" → "περίπου 100" + - Ellipsis: "1...5" → "1 ... 5" + - Multiplication/division (non-det): "10 x 20" → "10 επί 20", "10/20" → "10 δια 20" + - Number/NO.: "No. 12" → "Αριθμός 12" + + Args: + time: composed tagger and verbalizer (outputs spoken form directly) + date: composed tagger and verbalizer (outputs spoken form directly) + cardinal: tagger instance (uses graph_no_tokens) + deterministic: if True will provide a single transduction option, + for False multiple transduction are generated (used for audio-based normalization) + lm: whether to use for hybrid LM + """ + + def __init__( + self, + time: GraphFst, + date: GraphFst, + cardinal: GraphFst, + deterministic: bool = True, + lm: bool = False, + ): + super().__init__(name="range", kind="classify", deterministic=deterministic) + + delete_space = pynini.closure(pynutil.delete(" "), 0, 1) + + approx = pynini.cross("~", "περίπου") + + # TIME + time_graph = time + delete_space + pynini.cross("-", " προς ") + delete_space + time + self.graph = time_graph | (approx + time) + + cardinal_graph = cardinal.graph_no_tokens + + # YEAR + date_year_four_digit = (NEMO_DIGIT**4 + pynini.closure(pynini.accep("s"), 0, 1)) @ date + date_year_two_digit = (NEMO_DIGIT**2 + pynini.closure(pynini.accep("s"), 0, 1)) @ date + year_to_year_graph = ( + date_year_four_digit + + delete_space + + pynini.cross("-", " προς ") + + delete_space + + (date_year_four_digit | date_year_two_digit | (NEMO_DIGIT**2 @ cardinal_graph)) + ) + mid_year_graph = ( + pynini.cross("mid", "μέσα") + pynini.cross("-", " ") + (date_year_four_digit | date_year_two_digit) + ) + + self.graph |= year_to_year_graph + self.graph |= mid_year_graph + + # ADDITION / APPROX / ELLIPSIS + range_graph = cardinal_graph + pynini.closure(pynini.cross("+", " συν ") + cardinal_graph, 1) + range_graph |= cardinal_graph + pynini.closure(pynini.cross(" + ", " συν ") + cardinal_graph, 1) + range_graph |= approx + cardinal_graph + range_graph |= cardinal_graph + (pynini.cross("...", " ... ") | pynini.accep(" ... ")) + cardinal_graph + + if not deterministic or lm: + # cardinal ---- + cardinal_to_cardinal_graph = ( + cardinal_graph + + delete_space + + pynini.cross("-", pynini.union(" προς ", " μείον ")) + + delete_space + + cardinal_graph + ) + + range_graph |= cardinal_to_cardinal_graph | ( + cardinal_graph + delete_space + pynini.cross(":", " προς ") + delete_space + cardinal_graph + ) + + # MULTIPLY + for x in [" x ", "x"]: + range_graph |= cardinal_graph + pynini.cross(x, pynini.union(" επί ", " φορές ")) + cardinal_graph + + for x in [" x", "x"]: + range_graph |= cardinal_graph + pynini.cross(x, " φορές") + + # 5x to 7x → πέντε προς επτά x/φορές + range_graph |= ( + cardinal_graph + + pynutil.delete(x) + + pynini.union(" προς ", "-", " - ") + + cardinal_graph + + pynini.cross(x, pynini.union(" x", " φορές")) + ) + + for x in ["*", " * "]: + range_graph |= cardinal_graph + pynini.closure(pynini.cross(x, " φορές ") + cardinal_graph, 1) + + # No. 12 → Αριθμός δώδεκα + range_graph |= ( + (pynini.cross(pynini.union("NO", "No"), "Αριθμός") | pynini.cross("no", "αριθμός")) + + pynini.closure(pynini.union(". ", " "), 0, 1) + + cardinal_graph + ) + + for x in ["/", " / "]: + range_graph |= cardinal_graph + pynini.closure(pynini.cross(x, " δια ") + cardinal_graph, 1) + + # 10% to 20% → δέκα τοις εκατό προς είκοσι τοις εκατό + range_graph |= ( + cardinal_graph + + pynini.closure(pynini.cross("%", " τοις εκατό") | pynutil.delete("%"), 0, 1) + + pynini.union(" προς ", "-", " - ") + + cardinal_graph + + pynini.cross("%", " τοις εκατό") + ) + + self.graph |= range_graph + + self.graph = self.graph.optimize() + graph = pynutil.insert("name: \"") + convert_space(self.graph).optimize() + pynutil.insert("\"") + self.fst = graph.optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/roman.py b/nemo_text_processing/text_normalization/el/taggers/roman.py new file mode 100644 index 000000000..b7aa1355b --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/roman.py @@ -0,0 +1,116 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.utils import get_abs_path, load_labels +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_ALPHA, NEMO_SIGMA, GraphFst + + +class RomanFst(GraphFst): + """ + Finite state transducer for classifying roman numbers in Greek, + e.g. "IV" -> tokens { roman { integer: "τέσσερα" } } + e.g. "Elizabeth II" -> tokens { roman { key_the_ordinal: "Elizabeth" integer: "δεύτερη" } } + e.g. "Κεφάλαιο V" -> tokens { roman { key_cardinal: "Κεφάλαιο" integer: "πέντε" } } + + Args: + cardinal: CardinalFst + ordinal: OrdinalFst + deterministic: if True will provide a single transduction option, + for False multiple transduction are generated (used for audio-based normalization) + lm: if True will provide a single transduction option for language model + """ + + def __init__(self, cardinal=None, ordinal=None, deterministic: bool = True, lm: bool = False): + super().__init__(name="roman", kind="classify", deterministic=deterministic) + + roman_dict = load_labels(get_abs_path("data/roman/roman_to_spoken.tsv")) + default_graph = pynini.string_map(roman_dict).optimize() + default_graph = pynutil.insert("integer: \"") + default_graph + pynutil.insert("\"") + ordinal_limit = 19 + + # graph for teen Roman numerals (I-XIX) + graph_teens = pynini.string_map([x[0] for x in roman_dict[1:ordinal_limit]]).optimize() + + # Names (male + female) + names = get_names() + + # Name + teen Roman → ordinal ("Elizabeth II" → "Ελισάβετ δεύτερη") + graph = ( + pynutil.insert("key_the_ordinal: \"") + + names + + pynutil.insert("\"") + + pynini.accep(" ") + + graph_teens @ default_graph + ).optimize() + + # Key words + Roman → cardinal ("Κεφάλαιο V" → "Κεφάλαιο πέντε") + key_words = [] + for k_word in load_labels(get_abs_path("data/roman/key_word.tsv")): + key_words.append(k_word) + key_words.append([k_word[0][0].upper() + k_word[0][1:]]) + key_words.append([k_word[0].upper()]) + + key_words = pynini.string_map(key_words).optimize() + graph |= ( + pynutil.insert("key_cardinal: \"") + key_words + pynutil.insert("\"") + pynini.accep(" ") + default_graph + ).optimize() + + if deterministic or lm: + # two digit roman numerals up to 49 + roman_to_cardinal = pynini.compose( + pynini.closure(NEMO_ALPHA, 2), + ( + pynutil.insert("default_cardinal: \"default\" ") + + (pynini.string_map([x[0] for x in roman_dict[:50]]).optimize()) @ default_graph + ), + ) + graph |= roman_to_cardinal + elif not lm: + # two or more digit roman numerals + roman_to_cardinal = pynini.compose( + pynini.difference(NEMO_SIGMA, "I"), + ( + pynutil.insert("default_cardinal: \"default\" integer: \"") + + pynini.string_map(roman_dict).optimize() + + pynutil.insert("\"") + ), + ).optimize() + graph |= roman_to_cardinal + + # convert three digit roman or up with suffix to ordinal + roman_to_ordinal = pynini.compose( + pynini.closure(NEMO_ALPHA, 3), + (pynutil.insert("default_ordinal: \"default\" ") + graph_teens @ default_graph + pynutil.delete("th")), + ) + + graph |= roman_to_ordinal + graph = self.add_tokens(graph.optimize()) + + self.fst = graph.optimize() + + +def get_names(): + """ + Returns the graph that matched common male and female names. + """ + male_labels = load_labels(get_abs_path("data/roman/male.tsv")) + female_labels = load_labels(get_abs_path("data/roman/female.tsv")) + male_labels.extend([[x[0].upper()] for x in male_labels]) + female_labels.extend([[x[0].upper()] for x in female_labels]) + names = pynini.string_map(male_labels).optimize() + names |= pynini.string_map(female_labels).optimize() + return names diff --git a/nemo_text_processing/text_normalization/el/taggers/serial.py b/nemo_text_processing/text_normalization/el/taggers/serial.py new file mode 100644 index 000000000..8d41698c0 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/serial.py @@ -0,0 +1,250 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.examples import plurals +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.utils import get_abs_path, load_labels +from nemo_text_processing.text_normalization.en.graph_utils import ( + MIN_NEG_WEIGHT, + MIN_POS_WEIGHT, + NEMO_ALPHA, + NEMO_DIGIT, + NEMO_NOT_SPACE, + NEMO_SIGMA, + GraphFst, + convert_space, +) + + +def _leading_zero_graph(cardinal: GraphFst) -> "pynini.FstLike": + return pynini.compose(pynini.accep("0") + pynini.closure(NEMO_DIGIT), cardinal.single_digits_graph).optimize() + + +def _build_serial_graph( + num_graph: "pynini.FstLike", + delimiter: "pynini.FstLike", + alphas: "pynini.FstLike", +) -> "pynini.FstLike": + letter_num = alphas + delimiter + num_graph + num_letter = pynini.closure(num_graph + delimiter, 1) + alphas + next_alpha_or_num = pynini.closure(delimiter + (alphas | num_graph)) + next_alpha_or_num |= pynini.closure( + delimiter + + num_graph + + plurals._priority_union(pynini.accep(" "), pynutil.insert(" "), NEMO_SIGMA).optimize() + + alphas + ) + + serial_graph = letter_num + next_alpha_or_num + serial_graph |= num_letter + next_alpha_or_num + serial_graph |= num_graph + delimiter + num_graph + delimiter + num_graph + pynini.closure(delimiter + num_graph) + + symbols = [x[0] for x in load_labels(get_abs_path("data/whitelist/symbol.tsv"))] + symbols = pynini.union(*symbols) + serial_graph |= pynini.compose(NEMO_SIGMA + symbols + NEMO_SIGMA, num_graph + delimiter + num_graph) + + serial_graph = pynutil.add_weight(serial_graph, MIN_POS_WEIGHT) + serial_graph |= ( + pynini.closure(NEMO_NOT_SPACE, 1) + (pynini.cross("^2", " squared") | pynini.cross("^3", " cubed")).optimize() + ) + + serial_graph = ( + pynini.closure((serial_graph | num_graph | alphas) + delimiter) + + serial_graph + + pynini.closure(delimiter + (serial_graph | num_graph | alphas)) + ) + return serial_graph.optimize() + + +class SerialFst(GraphFst): + """ + Finite state transducer for classifying serial numbers in Greek, + e.g. + "A320" -> tokens { name: "άλφα τριακόσια είκοσι" } + "H800" -> tokens { name: "έιτς οχτακόσια" } + "a320b" -> tokens { name: "άλφα τρία δύο μηδέν βήτα" } + + Args: + cardinal: Greek cardinal tagger instance. + deterministic: if True will provide a single transduction option, + for False multiple transduction are generated (used for audio-based normalization) + lm: whether to use for hybrid LM + """ + + def __init__(self, cardinal: GraphFst, deterministic: bool = True, lm: bool = False): + super().__init__(name="serial", kind="classify", deterministic=deterministic) + + cardinal_graph = cardinal.graph_no_tokens + + # ---- Symbol support ---- + symbols_graph = pynini.string_file(get_abs_path("data/whitelist/symbol.tsv")).optimize() + symbols_graph |= pynini.cross("#", "hash") + + if deterministic: + num_graph_pure = ( + pynini.compose(NEMO_DIGIT ** (1, 3), cardinal_graph) # 1-3 digits → cardinal speech + | pynini.compose(NEMO_DIGIT ** (4, ...), cardinal.single_digits_graph) # 4+ digits → single-digit + | _leading_zero_graph(cardinal) + ).optimize() + + num_graph_alnum = ( + pynini.compose(NEMO_DIGIT, cardinal_graph) # single digit → cardinal + | pynini.compose(NEMO_DIGIT**2, cardinal_graph) # two digits → cardinal + | pynutil.add_weight( + pynini.compose(NEMO_DIGIT + pynini.closure("0", 1), cardinal_graph), + MIN_NEG_WEIGHT, # X0 → cardinal + ) + | pynini.compose( + pynini.difference(NEMO_DIGIT**3, NEMO_DIGIT + NEMO_DIGIT + "00"), + cardinal.single_digits_graph, # 3-digit not ending in 00 → single-digit + ) + | pynini.compose(NEMO_DIGIT ** (4, ...), cardinal.single_digits_graph) # 4+ digits → single-digit + | _leading_zero_graph(cardinal) + ).optimize() + + num_graph_slash = ( + pynini.compose(NEMO_DIGIT ** (1, 4), cardinal_graph) + | pynini.compose(NEMO_DIGIT ** (5, ...), cardinal.single_digits_graph) + | _leading_zero_graph(cardinal) + ).optimize() + + else: + num_graph_pure = cardinal.final_graph + num_graph_alnum = cardinal.final_graph + num_graph_slash = cardinal.final_graph + + num_graph_pure |= symbols_graph + num_graph_alnum |= symbols_graph + + if not self.deterministic and not lm: + num_graph_pure |= cardinal.single_digits_graph + num_graph_pure |= pynini.compose(num_graph_pure, NEMO_SIGMA + pynutil.delete("hundred ") + NEMO_SIGMA) + num_graph_pure |= pynutil.add_weight( + NEMO_DIGIT**2 @ cardinal.graph_hundred_component_at_least_one_none_zero_digit, + weight=MIN_POS_WEIGHT, + ) + num_graph_alnum = num_graph_pure + + # add space between letter and digit/symbol + symbols = [x[0] for x in load_labels(get_abs_path("data/whitelist/symbol.tsv"))] + symbols = pynini.union(*symbols) + digit_symbol = NEMO_DIGIT | symbols + + graph_with_space = pynini.compose( + pynini.cdrewrite(pynutil.insert(" "), NEMO_ALPHA | symbols, digit_symbol, NEMO_SIGMA), + pynini.cdrewrite(pynutil.insert(" "), digit_symbol, NEMO_ALPHA | symbols, NEMO_SIGMA), + ) + graph_with_space = pynini.compose( + graph_with_space, + pynini.cdrewrite(pynutil.insert(" "), NEMO_ALPHA, NEMO_ALPHA, NEMO_SIGMA), + ) + + # ---- Greek letter names ---- + alphas = _get_alpha_map() + + # serial graph with delimiter + delimiter = pynini.accep("-") | pynini.accep("/") | pynini.accep(" ") + if not deterministic: + delimiter |= pynini.cross("-", " dash ") | pynini.cross("/", " slash ") + + serial_graph = _build_serial_graph(num_graph_pure, delimiter, alphas) + serial_graph_alnum = _build_serial_graph(num_graph_alnum, delimiter, alphas) + + # Rule 3: tokens that contain only digits and slashes (e.g. 31/31/100, 123/261788/2021). + slash_digit_token = ( + pynini.closure(NEMO_DIGIT, 1) + pynini.accep("/") + pynini.closure(NEMO_DIGIT | pynini.accep("/"), 0) + ) + slash_serial = pynini.compose( + slash_digit_token, + pynini.closure(num_graph_slash + pynini.accep("/"), 1) + num_graph_slash, + ).optimize() + serial_graph |= pynutil.add_weight(slash_serial, MIN_NEG_WEIGHT) + + serial_graph |= pynini.compose(graph_with_space, serial_graph_alnum.optimize()).optimize() + serial_graph = pynini.compose(pynini.closure(NEMO_NOT_SPACE, 2), serial_graph).optimize() + + # exclude patterns like "import/export" + serial_graph = pynini.compose( + pynini.difference( + NEMO_SIGMA, + pynini.closure(NEMO_ALPHA, 1) + pynini.accep("/") + pynini.closure(NEMO_ALPHA, 1), + ), + serial_graph, + ) + + self.graph = serial_graph.optimize() + graph = pynutil.insert("name: \"") + convert_space(self.graph).optimize() + pynutil.insert("\"") + self.fst = graph.optimize() + + +def _get_alpha_map(): + letter_names = pynini.string_map( + [ + ("a", "άλφα"), + ("A", "άλφα"), + ("b", "βήτα"), + ("B", "βήτα"), + ("c", "γάμα"), + ("C", "γάμα"), + ("d", "δέλτα"), + ("D", "δέλτα"), + ("e", "έψιλον"), + ("E", "έψιλον"), + ("f", "εφ"), + ("F", "εφ"), + ("g", "τζι"), + ("G", "τζι"), + ("h", "έιτς"), + ("H", "έιτς"), + ("i", "γιώτα"), + ("I", "γιώτα"), + ("j", "τζέι"), + ("J", "τζέι"), + ("k", "κάπα"), + ("K", "κάπα"), + ("l", "ελ"), + ("L", "ελ"), + ("m", "εμ"), + ("M", "εμ"), + ("n", "εν"), + ("N", "εν"), + ("o", "όμικρον"), + ("O", "όμικρον"), + ("p", "πι"), + ("P", "πι"), + ("q", "κιου"), + ("Q", "κιου"), + ("r", "αρ"), + ("R", "αρ"), + ("s", "ες"), + ("S", "ες"), + ("t", "ταυ"), + ("T", "ταυ"), + ("u", "ύψιλον"), + ("U", "ύψιλον"), + ("v", "βι"), + ("V", "βι"), + ("w", "νταμπλγιου"), + ("W", "νταμπλγιου"), + ("x", "χι"), + ("X", "χι"), + ("y", "γουάι"), + ("Y", "γουάι"), + ("z", "ζέτα"), + ("Z", "ζέτα"), + ] + ).optimize() + return pynini.closure(letter_names, 1) diff --git a/nemo_text_processing/text_normalization/el/taggers/telephone.py b/nemo_text_processing/text_normalization/el/taggers/telephone.py new file mode 100644 index 000000000..8909586cf --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/telephone.py @@ -0,0 +1,47 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import ( + NEMO_DIGIT, + NEMO_NOT_QUOTE, + GraphFst, + delete_space, + insert_space, +) + + +class TelephoneFst(GraphFst): + def __init__(self, cardinal: GraphFst, deterministic: bool = True): + super().__init__(name="telephone", kind="classify", deterministic=deterministic) + + cardinal_graph = cardinal.graph_no_tokens + digit = pynini.compose( + pynini.union("0", NEMO_DIGIT), + cardinal_graph, + ).optimize() + + separator = pynini.union("-", ".", " ") + digit_group = (digit + pynini.closure(insert_space + digit)).optimize() + + number_part = ( + digit_group + + pynini.cross(separator, " ") + + digit_group + + pynini.closure(pynini.cross(separator, " ") + digit_group) + ) + number_part = pynutil.insert("number_part: \"") + number_part + pynutil.insert("\"") + + tagger_graph = number_part.optimize() + + number_v = ( + pynutil.delete("number_part:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + verbalizer_graph = number_v + delete_space + + self.final_graph = (tagger_graph @ verbalizer_graph).optimize() + self.fst = pynutil.insert("number_part: \"") + self.final_graph + pynutil.insert("\"") + self.fst = self.add_tokens(self.fst).optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/time.py b/nemo_text_processing/text_normalization/el/taggers/time.py new file mode 100644 index 000000000..5de386a49 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/time.py @@ -0,0 +1,71 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import ( + NEMO_DIGIT, + NEMO_NOT_QUOTE, + GraphFst, + delete_space, + insert_space, +) + + +class TimeFst(GraphFst): + def __init__(self, cardinal: GraphFst, deterministic: bool = True): + super().__init__(name="time", kind="classify", deterministic=deterministic) + + cardinal_graph = cardinal.graph_no_tokens + + delete_sep = pynutil.delete(":") + + hour = pynini.compose( + (pynutil.delete("0") + NEMO_DIGIT) | ("1" + NEMO_DIGIT) | ("2" + pynini.union("0", "1", "2", "3")), + cardinal_graph, + ).optimize() + + minutes = pynini.compose( + pynutil.delete("0") + NEMO_DIGIT, + cardinal_graph, + ) + minutes |= pynini.compose( + pynini.union(*"012345") + NEMO_DIGIT, + cardinal_graph, + ) + minutes = minutes.optimize() + + hm = ( + pynutil.insert("hours: \"") + + hour + + pynutil.insert("\"") + + delete_sep + + pynutil.insert("minutes: \"") + + minutes + + pynutil.insert("\"") + + pynutil.insert(" preserve_order: true") + ) + h = pynutil.add_weight(pynutil.insert("hours: \"") + hour + pynutil.insert("\"") + pynutil.delete(":00"), 0.1) + tagger_graph = (hm | h).optimize() + + hour_v = ( + pynutil.delete("hours:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + minutes_v = ( + pynutil.delete("minutes:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + + verbalizer_graph = ( + hour_v + delete_space + insert_space + minutes_v + delete_space + pynutil.delete("preserve_order: true") + ) + verbalizer_graph |= hour_v + delete_space + + self.final_graph = (tagger_graph @ verbalizer_graph).optimize() + self.fst = pynutil.insert("hours: \"") + self.final_graph + pynutil.insert("\"") + self.fst = self.add_tokens(self.fst).optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/tokenize_and_classify.py b/nemo_text_processing/text_normalization/el/taggers/tokenize_and_classify.py new file mode 100644 index 000000000..6af3983c1 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/tokenize_and_classify.py @@ -0,0 +1,148 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.taggers.cardinal import CardinalFst +from nemo_text_processing.text_normalization.el.taggers.date import DateFst +from nemo_text_processing.text_normalization.el.taggers.decimal import DecimalFst +from nemo_text_processing.text_normalization.el.taggers.electronic import ElectronicFst +from nemo_text_processing.text_normalization.el.taggers.fraction import FractionFst +from nemo_text_processing.text_normalization.el.taggers.measure import MeasureFst +from nemo_text_processing.text_normalization.el.taggers.money import MoneyFst +from nemo_text_processing.text_normalization.el.taggers.ordinal import OrdinalFst +from nemo_text_processing.text_normalization.el.taggers.punctuation import PunctuationFst +from nemo_text_processing.text_normalization.el.taggers.range import RangeFst +from nemo_text_processing.text_normalization.el.taggers.roman import RomanFst +from nemo_text_processing.text_normalization.el.taggers.serial import SerialFst +from nemo_text_processing.text_normalization.el.taggers.telephone import TelephoneFst +from nemo_text_processing.text_normalization.el.taggers.time import TimeFst +from nemo_text_processing.text_normalization.el.taggers.whitelist import WhiteListFst +from nemo_text_processing.text_normalization.el.taggers.word import WordFst +from nemo_text_processing.text_normalization.el.verbalizers.date import DateFst as vDateFst +from nemo_text_processing.text_normalization.el.verbalizers.time import TimeFst as vTimeFst +from nemo_text_processing.text_normalization.en.graph_utils import ( + INPUT_LOWER_CASED, + GraphFst, + delete_extra_space, + delete_space, + generator_main, +) +from nemo_text_processing.utils.logging import logger + + +class ClassifyFst(GraphFst): + """ + Final class that composes all other classification grammars. This class can process an + entire sentence. For deployment, this grammar will be compiled and exported to OpenFst + Finite State Archive (FAR) File. + + Args: + input_case: accepting either "lower_cased" or "cased" input. + deterministic: if True will provide a single transduction option, + for False multiple options (used for audio-based normalization) + cache_dir: path to a dir with .far grammar file. Set to None to avoid using cache. + overwrite_cache: set to True to overwrite .far files + whitelist: path to a file with whitelist replacements + """ + + def __init__( + self, + input_case: str = INPUT_LOWER_CASED, + deterministic: bool = True, + cache_dir: str = None, + overwrite_cache: bool = False, + whitelist: str = None, + ): + super().__init__(name="tokenize_and_classify", kind="classify", deterministic=deterministic) + + far_file = None + if cache_dir is not None and cache_dir != "None": + os.makedirs(cache_dir, exist_ok=True) + far_file = os.path.join(cache_dir, f"_el_tn_{input_case}_{deterministic}_deterministic.far") + if not overwrite_cache and far_file and os.path.exists(far_file): + self.fst = pynini.Far(far_file, mode="r")["tokenize_and_classify"] + logger.info(f"ClassifyFst.fst was restored from {far_file}.") + else: + logger.info("Creating ClassifyFst grammars.") + + cardinal = CardinalFst(deterministic=deterministic) + cardinal_graph = cardinal.fst + + ordinal = OrdinalFst(cardinal=cardinal, deterministic=deterministic) + ordinal_graph = ordinal.fst + + decimal = DecimalFst(cardinal=cardinal, deterministic=deterministic) + decimal_graph = decimal.fst + + fraction = FractionFst(cardinal=cardinal, ordinal=ordinal, deterministic=deterministic) + fraction_graph = fraction.fst + + date_graph = DateFst(cardinal=cardinal, deterministic=deterministic).fst + time_graph = TimeFst(cardinal=cardinal, deterministic=deterministic).fst + measure_graph = MeasureFst(cardinal=cardinal, decimal=decimal, deterministic=deterministic).fst + telephone_graph = TelephoneFst(cardinal=cardinal, deterministic=deterministic).fst + electronic_graph = ElectronicFst(deterministic=deterministic).fst + money_graph = MoneyFst(cardinal=cardinal, decimal=decimal, deterministic=deterministic).fst + serial_graph = SerialFst(cardinal=cardinal, deterministic=deterministic).fst + + v_time_fst = vTimeFst(deterministic=deterministic).fst + v_date_fst = vDateFst(deterministic=deterministic).fst + time_final = pynini.compose(time_graph, v_time_fst) + date_final = pynini.compose(date_graph, v_date_fst) + range_graph = RangeFst( + time=time_final, date=date_final, cardinal=cardinal, deterministic=deterministic + ).fst + + word_graph = WordFst().fst + punct_graph = PunctuationFst().fst + whitelist_graph = WhiteListFst( + input_case=input_case, deterministic=deterministic, input_file=whitelist + ).fst + roman_graph = RomanFst(cardinal=cardinal, ordinal=ordinal, deterministic=deterministic).fst + + classify = ( + pynutil.add_weight(whitelist_graph, 1.01) + | pynutil.add_weight(date_graph, 1.08) + | pynutil.add_weight(decimal_graph, 1.1) + | pynutil.add_weight(time_graph, 1.1) + | pynutil.add_weight(cardinal_graph, 1.1) + | pynutil.add_weight(ordinal_graph, 1.1) + | pynutil.add_weight(fraction_graph, 1.1) + | pynutil.add_weight(money_graph, 1.2) + | pynutil.add_weight(measure_graph, 1.2) + | pynutil.add_weight(roman_graph, 1.2) + | pynutil.add_weight(telephone_graph, 1.3) + | pynutil.add_weight(serial_graph, 1.3) + | pynutil.add_weight(electronic_graph, 1.3) + | pynutil.add_weight(word_graph, 100) + ) + + punct = pynutil.insert("tokens { ") + pynutil.add_weight(punct_graph, weight=1.1) + pynutil.insert(" }") + token = pynutil.insert("tokens { ") + classify + pynutil.insert(" }") + token_plus_punct = ( + pynini.closure(punct + pynutil.insert(" ")) + token + pynini.closure(pynutil.insert(" ") + punct) + ) + + graph = token_plus_punct + pynini.closure(delete_extra_space + token_plus_punct) + graph = delete_space + graph + delete_space + + self.fst = graph.optimize() + + if far_file: + generator_main(far_file, {"tokenize_and_classify": self.fst}) + logger.info(f"ClassifyFst grammars are saved to {far_file}.") diff --git a/nemo_text_processing/text_normalization/el/taggers/whitelist.py b/nemo_text_processing/text_normalization/el/taggers/whitelist.py new file mode 100644 index 000000000..e47e12b9c --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/whitelist.py @@ -0,0 +1,49 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.utils import get_abs_path +from nemo_text_processing.text_normalization.en.graph_utils import GraphFst, convert_space, load_labels + + +class WhiteListFst(GraphFst): + """ + Finite state transducer for classifying whitelist, e.g. + κ.λπ. -> tokens { name: "και τα λοιπά" } + This class has highest priority among all classifier grammars. Whitelisted tokens are + defined and loaded from "data/whitelist.tsv". + + Args: + input_case: accepting either "lower_cased" or "cased" input. + deterministic: if True will provide a single transduction option, + for False multiple options (used for audio-based normalization) + input_file: path to a file with whitelist replacements + """ + + def __init__(self, input_case: str, deterministic: bool = True, input_file: str = None): + super().__init__(name="whitelist", kind="classify") + + def _get_whitelist_graph(file): + whitelist = load_labels(file) + whitelist = [[x, y] for x, y in whitelist] + return pynini.string_map(whitelist) + + whitelist = pynini.string_file(get_abs_path("data/whitelist.tsv")) + if input_file: + whitelist |= _get_whitelist_graph(input_file) + + graph = pynutil.insert("name: \"") + convert_space(whitelist) + pynutil.insert("\"") + self.fst = graph.optimize() diff --git a/nemo_text_processing/text_normalization/el/taggers/word.py b/nemo_text_processing/text_normalization/el/taggers/word.py new file mode 100644 index 000000000..04df9cf0c --- /dev/null +++ b/nemo_text_processing/text_normalization/el/taggers/word.py @@ -0,0 +1,30 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_SPACE, GraphFst + + +class WordFst(GraphFst): + """ + Finite state transducer for classifying word. + e.g. καλημέρα -> tokens { name: "καλημέρα" } + """ + + def __init__(self): + super().__init__(name="word", kind="classify") + word = pynutil.insert("name: \"") + pynini.closure(NEMO_NOT_SPACE, 1) + pynutil.insert("\"") + self.fst = word.optimize() diff --git a/nemo_text_processing/text_normalization/el/utils.py b/nemo_text_processing/text_normalization/el/utils.py new file mode 100644 index 000000000..4993678ad --- /dev/null +++ b/nemo_text_processing/text_normalization/el/utils.py @@ -0,0 +1,43 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import csv +import os + + +def get_abs_path(rel_path): + """ + Get absolute path + + Args: + rel_path: relative path to this file + + Returns absolute path + """ + return os.path.dirname(os.path.abspath(__file__)) + '/' + rel_path + + +def load_labels(abs_path): + """ + loads relative path file as dictionary + + Args: + abs_path: absolute path + + Returns dictionary of mappings + """ + label_tsv = open(abs_path, encoding="utf-8") + labels = list(csv.reader(label_tsv, delimiter="\t")) + label_tsv.close() + return labels diff --git a/nemo_text_processing/text_normalization/el/verbalizers/__init__.py b/nemo_text_processing/text_normalization/el/verbalizers/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/el/verbalizers/cardinal.py b/nemo_text_processing/text_normalization/el/verbalizers/cardinal.py new file mode 100644 index 000000000..8581541d7 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/cardinal.py @@ -0,0 +1,32 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst + + +class CardinalFst(GraphFst): + """ + Finite state transducer for verbalizing cardinal, e.g. + cardinal { integer: "πενήντα πέντε" } -> πενήντα πέντε + """ + + def __init__(self, deterministic: bool = True): + super().__init__(name="cardinal", kind="verbalize", deterministic=deterministic) + + number = pynutil.delete("integer: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + self.numbers = number + self.fst = self.delete_tokens(number).optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/date.py b/nemo_text_processing/text_normalization/el/verbalizers/date.py new file mode 100644 index 000000000..4403a4b6a --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/date.py @@ -0,0 +1,41 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class DateFst(GraphFst): + def __init__(self, deterministic: bool = True): + super().__init__(name="date", kind="verbalize", deterministic=deterministic) + + day = ( + pynutil.delete("day:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + month = ( + pynutil.delete("month:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + year = ( + pynutil.delete("year:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + graph_dmy = ( + day + + delete_space + + pynutil.insert(" ") + + month + + pynini.closure(delete_space + pynutil.insert(" ") + year, 0, 1) + ) + graph = graph_dmy | year + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/decimal.py b/nemo_text_processing/text_normalization/el/verbalizers/decimal.py new file mode 100644 index 000000000..10a6f0bbd --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/decimal.py @@ -0,0 +1,37 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class DecimalFst(GraphFst): + """ + Finite state transducer for verbalizing decimal, e.g. + decimal { integer_part: "τρία" fractional_part: "πέντε" } -> τρία κόμμα πέντε + """ + + def __init__(self, deterministic: bool = True): + super().__init__(name="decimal", kind="verbalize", deterministic=deterministic) + + optional_sign = pynini.closure(pynini.cross("negative: \"true\"", "μείον ") + delete_space, 0, 1) + integer = pynutil.delete("integer_part: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + fractional = pynutil.delete("fractional_part: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + + graph = integer + delete_space + pynutil.insert(" κόμμα ") + fractional + graph = optional_sign + graph + self.numbers = graph + self.fst = self.delete_tokens(graph).optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/electronic.py b/nemo_text_processing/text_normalization/el/verbalizers/electronic.py new file mode 100644 index 000000000..30f7d9e5e --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/electronic.py @@ -0,0 +1,19 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class ElectronicFst(GraphFst): + def __init__(self, deterministic: bool = True): + super().__init__(name="electronic", kind="verbalize", deterministic=deterministic) + + graph = ( + pynutil.delete("username:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/fraction.py b/nemo_text_processing/text_normalization/el/verbalizers/fraction.py new file mode 100644 index 000000000..504a68bce --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/fraction.py @@ -0,0 +1,36 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class FractionFst(GraphFst): + """ + Finite state transducer for verbalizing fraction, e.g. + fraction { numerator: "τρία" denominator: "τέταρτα" } -> τρία τέταρτα + """ + + def __init__(self, deterministic: bool = True): + super().__init__(name="fraction", kind="verbalize", deterministic=deterministic) + + optional_sign = pynini.closure(pynini.cross("negative: \"true\"", "μείον ") + delete_space, 0, 1) + numerator = pynutil.delete("numerator: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + denominator = pynutil.delete("denominator: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + + graph = numerator + delete_space + pynutil.insert(" ") + denominator + graph = optional_sign + graph + self.fst = self.delete_tokens(graph).optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/measure.py b/nemo_text_processing/text_normalization/el/verbalizers/measure.py new file mode 100644 index 000000000..cdb1904f9 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/measure.py @@ -0,0 +1,46 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class MeasureFst(GraphFst): + def __init__(self, deterministic: bool = True): + super().__init__(name="measure", kind="verbalize", deterministic=deterministic) + + integer = ( + pynutil.delete("integer:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + graph = integer + delete_space + + preserve_order = pynutil.delete("preserve_order:") + delete_space + pynutil.delete("true") + delete_space + + graph_cardinal = ( + pynutil.delete("cardinal {") + + delete_space + + pynutil.delete("integer: \"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + + delete_space + + pynutil.delete("}") + ) + + units = pynutil.delete(pynini.union("units: \"address\" ", "units: \"math\" ")) + + graph |= units + graph_cardinal + delete_space + pynini.closure(preserve_order) + + address_flat = ( + pynutil.delete("units: \"address\" ") + + delete_space + + integer + + delete_space + + pynini.closure(preserve_order) + ) + graph |= address_flat + + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/money.py b/nemo_text_processing/text_normalization/el/verbalizers/money.py new file mode 100644 index 000000000..d86710c3a --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/money.py @@ -0,0 +1,27 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class MoneyFst(GraphFst): + def __init__(self, deterministic: bool = True): + super().__init__(name="money", kind="verbalize", deterministic=deterministic) + + integer = ( + pynutil.delete("integer_part:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + fractional = ( + pynutil.delete("fractional_part:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + graph = integer + pynini.closure(delete_space + pynutil.insert(" ") + fractional, 0, 1) + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/ordinal.py b/nemo_text_processing/text_normalization/el/verbalizers/ordinal.py new file mode 100644 index 000000000..0272bdd06 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/ordinal.py @@ -0,0 +1,31 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst + + +class OrdinalFst(GraphFst): + """ + Finite state transducer for verbalizing ordinal, e.g. + ordinal { integer: "εικοστή πρώτη" } -> εικοστή πρώτη + """ + + def __init__(self, deterministic: bool = True): + super().__init__(name="ordinal", kind="verbalize", deterministic=deterministic) + + graph = pynutil.delete("integer: \"") + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete("\"") + self.fst = self.delete_tokens(graph).optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/roman.py b/nemo_text_processing/text_normalization/el/verbalizers/roman.py new file mode 100644 index 000000000..a15c28142 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/roman.py @@ -0,0 +1,69 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst + + +class RomanFst(GraphFst): + """ + Finite state transducer for verbalizing roman numerals in Greek, + e.g. tokens { roman { integer: "ένα" } } -> ένα + + Args: + deterministic: if True will provide a single transduction option, + for False multiple transduction are generated (used for audio-based normalization) + """ + + def __init__(self, deterministic: bool = True): + super().__init__(name="roman", kind="verbalize", deterministic=deterministic) + + cardinal = pynini.closure(NEMO_NOT_QUOTE) + + # key_cardinal: "Κεφάλαιο" integer: "πέντε" -> Κεφάλαιο πέντε + graph = ( + pynutil.delete("key_cardinal: \"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + + pynini.accep(" ") + + pynutil.delete("integer: \"") + + cardinal + + pynutil.delete("\"") + ).optimize() + + # default_cardinal: "default" integer: "δέκα" -> δέκα + graph |= ( + pynutil.delete("default_cardinal: \"default\" integer: \"") + cardinal + pynutil.delete("\"") + ).optimize() + + # default_ordinal: "default" integer: "πέντε" -> πέντε + graph |= ( + pynutil.delete("default_ordinal: \"default\" integer: \"") + cardinal + pynutil.delete("\"") + ).optimize() + + # key_the_ordinal: "Ελισάβετ" integer: "δύο" -> Ελισάβετ δύο + graph |= ( + pynutil.delete("key_the_ordinal: \"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + + pynini.accep(" ") + + pynutil.delete("integer: \"") + + cardinal + + pynutil.delete("\"") + ).optimize() + + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/telephone.py b/nemo_text_processing/text_normalization/el/verbalizers/telephone.py new file mode 100644 index 000000000..47ea02d83 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/telephone.py @@ -0,0 +1,19 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class TelephoneFst(GraphFst): + def __init__(self, deterministic: bool = True): + super().__init__(name="telephone", kind="verbalize", deterministic=deterministic) + + graph = ( + pynutil.delete("number_part:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/time.py b/nemo_text_processing/text_normalization/el/verbalizers/time.py new file mode 100644 index 000000000..7f04af137 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/time.py @@ -0,0 +1,30 @@ +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space + + +class TimeFst(GraphFst): + def __init__(self, deterministic: bool = True): + super().__init__(name="time", kind="verbalize", deterministic=deterministic) + + hour = ( + pynutil.delete("hours:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + minutes = ( + pynutil.delete("minutes:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_NOT_QUOTE, 1) + + pynutil.delete("\"") + ) + preserve_order = pynutil.delete("preserve_order:") + delete_space + pynutil.delete("true") + + graph = hour + delete_space + pynutil.insert(" ") + minutes + delete_space + preserve_order + graph |= hour + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/verbalize.py b/nemo_text_processing/text_normalization/el/verbalizers/verbalize.py new file mode 100644 index 000000000..3eb7a7613 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/verbalize.py @@ -0,0 +1,68 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from nemo_text_processing.text_normalization.el.verbalizers.cardinal import CardinalFst +from nemo_text_processing.text_normalization.el.verbalizers.date import DateFst +from nemo_text_processing.text_normalization.el.verbalizers.decimal import DecimalFst +from nemo_text_processing.text_normalization.el.verbalizers.electronic import ElectronicFst +from nemo_text_processing.text_normalization.el.verbalizers.fraction import FractionFst +from nemo_text_processing.text_normalization.el.verbalizers.measure import MeasureFst +from nemo_text_processing.text_normalization.el.verbalizers.money import MoneyFst +from nemo_text_processing.text_normalization.el.verbalizers.ordinal import OrdinalFst +from nemo_text_processing.text_normalization.el.verbalizers.roman import RomanFst +from nemo_text_processing.text_normalization.el.verbalizers.telephone import TelephoneFst +from nemo_text_processing.text_normalization.el.verbalizers.time import TimeFst +from nemo_text_processing.text_normalization.el.verbalizers.whitelist import WhiteListFst +from nemo_text_processing.text_normalization.en.graph_utils import GraphFst + + +class VerbalizeFst(GraphFst): + """ + Composes other verbalizer grammars. For deployment, this grammar will be compiled and + exported to OpenFst Finite State Archive (FAR) File. + + Args: + deterministic: if True will provide a single transduction option, + for False multiple options (used for audio-based normalization) + """ + + def __init__(self, deterministic: bool = True): + super().__init__(name="verbalize", kind="verbalize", deterministic=deterministic) + cardinal_graph = CardinalFst(deterministic=deterministic).fst + ordinal_graph = OrdinalFst(deterministic=deterministic).fst + decimal_graph = DecimalFst(deterministic=deterministic).fst + fraction_graph = FractionFst(deterministic=deterministic).fst + money_graph = MoneyFst(deterministic=deterministic).fst + date_graph = DateFst(deterministic=deterministic).fst + time_graph = TimeFst(deterministic=deterministic).fst + measure_graph = MeasureFst(deterministic=deterministic).fst + telephone_graph = TelephoneFst(deterministic=deterministic).fst + electronic_graph = ElectronicFst(deterministic=deterministic).fst + whitelist_graph = WhiteListFst().fst + roman_graph = RomanFst(deterministic=deterministic).fst + graph = ( + cardinal_graph + | ordinal_graph + | decimal_graph + | fraction_graph + | money_graph + | date_graph + | time_graph + | measure_graph + | telephone_graph + | electronic_graph + | whitelist_graph + | roman_graph + ) + self.fst = graph.optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/verbalize_final.py b/nemo_text_processing/text_normalization/el/verbalizers/verbalize_final.py new file mode 100644 index 000000000..e2bb4e712 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/verbalize_final.py @@ -0,0 +1,51 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright 2015 and onwards Google, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.el.verbalizers.verbalize import VerbalizeFst +from nemo_text_processing.text_normalization.el.verbalizers.word import WordFst +from nemo_text_processing.text_normalization.en.graph_utils import GraphFst, delete_extra_space, delete_space + + +class VerbalizeFinalFst(GraphFst): + """ + Finite state transducer that verbalizes an entire sentence, e.g. + tokens { name: "στις" } tokens { cardinal { integer: "τρεις" } } tokens { name: "." } -> στις τρεις . + + Args: + deterministic: if True will provide a single transduction option, + for False multiple options (used for audio-based normalization) + cache_dir: path to a dir with .far grammar file. Set to None to avoid using cache. + overwrite_cache: set to True to overwrite .far files + """ + + def __init__(self, deterministic: bool = True, cache_dir: str = None, overwrite_cache: bool = False): + super().__init__(name="verbalize_final", kind="verbalize", deterministic=deterministic) + verbalize = VerbalizeFst(deterministic=deterministic).fst + word = WordFst().fst + types = verbalize | word + graph = ( + pynutil.delete("tokens") + + delete_space + + pynutil.delete("{") + + delete_space + + types + + delete_space + + pynutil.delete("}") + ) + graph = delete_space + pynini.closure(graph + delete_extra_space) + graph + delete_space + self.fst = graph diff --git a/nemo_text_processing/text_normalization/el/verbalizers/whitelist.py b/nemo_text_processing/text_normalization/el/verbalizers/whitelist.py new file mode 100644 index 000000000..5fd14153d --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/whitelist.py @@ -0,0 +1,37 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_CHAR, NEMO_SIGMA, GraphFst, delete_space + + +class WhiteListFst(GraphFst): + """ + Finite state transducer for verbalizing whitelist + e.g. tokens { name: "και τα λοιπά" } -> και τα λοιπά + """ + + def __init__(self): + super().__init__(name="whitelist", kind="verbalize") + graph = ( + pynutil.delete("name:") + + delete_space + + pynutil.delete("\"") + + pynini.closure(NEMO_CHAR - " ", 1) + + pynutil.delete("\"") + ) + graph = graph @ pynini.cdrewrite(pynini.cross("\u00a0", " "), "", "", NEMO_SIGMA) + self.fst = graph.optimize() diff --git a/nemo_text_processing/text_normalization/el/verbalizers/word.py b/nemo_text_processing/text_normalization/el/verbalizers/word.py new file mode 100644 index 000000000..6a7e82c94 --- /dev/null +++ b/nemo_text_processing/text_normalization/el/verbalizers/word.py @@ -0,0 +1,34 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright 2015 and onwards Google, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.en.graph_utils import NEMO_CHAR, NEMO_SIGMA, GraphFst, delete_space + + +class WordFst(GraphFst): + """ + Finite state transducer for verbalizing word + e.g. tokens { name: "καλημέρα" } -> καλημέρα + """ + + def __init__(self): + super().__init__(name="word", kind="verbalize") + chars = pynini.closure(NEMO_CHAR - " ", 1) + char = pynutil.delete("name:") + delete_space + pynutil.delete("\"") + chars + pynutil.delete("\"") + graph = char @ pynini.cdrewrite(pynini.cross("\u00a0", " "), "", "", NEMO_SIGMA) + + self.fst = graph.optimize() diff --git a/nemo_text_processing/text_normalization/normalize.py b/nemo_text_processing/text_normalization/normalize.py index d8ebf2f4d..5ddaffe54 100644 --- a/nemo_text_processing/text_normalization/normalize.py +++ b/nemo_text_processing/text_normalization/normalize.py @@ -172,6 +172,9 @@ def __init__( elif lang == 'hy': from nemo_text_processing.text_normalization.hy.taggers.tokenize_and_classify import ClassifyFst from nemo_text_processing.text_normalization.hy.verbalizers.verbalize_final import VerbalizeFinalFst + elif lang == 'el': + from nemo_text_processing.text_normalization.el.taggers.tokenize_and_classify import ClassifyFst + from nemo_text_processing.text_normalization.el.verbalizers.verbalize_final import VerbalizeFinalFst elif lang == 'rw': from nemo_text_processing.text_normalization.rw.taggers.tokenize_and_classify import ClassifyFst from nemo_text_processing.text_normalization.rw.verbalizers.verbalize_final import VerbalizeFinalFst diff --git a/tests/nemo_text_processing/el/__init__.py b/tests/nemo_text_processing/el/__init__.py new file mode 100644 index 000000000..9e3fb699d --- /dev/null +++ b/tests/nemo_text_processing/el/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_cardinal.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_cardinal.txt new file mode 100644 index 000000000..1047cb5b4 --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_cardinal.txt @@ -0,0 +1,45 @@ +δεκατρία~13 +δεκατρείς~13 +δεκατέσσερα~14 +δεκατέσσερις~14 +δεκατέσσερεις~14 +δεκαπέντε~15 +δεκαέξι~16 +δεκαεπτά~17 +δεκαοχτώ~18 +δεκαεννέα~19 +είκοσι~20 +τριάντα~30 +σαράντα~40 +πενήντα~50 +εξήντα~60 +εβδομήντα~70 +ογδόντα~80 +ενενήντα~90 +εκατό~100 +διακόσια~200 +τριακόσια~300 +τετρακόσια~400 +πεντακόσια~500 +εξακόσια~600 +εφτακόσια~700 +οκτακόσια~800 +εννιακόσια~900 +χίλια~1000 +δύο χιλιάδες~2000 +τριακόσια σαράντα πέντε~345 +εκατό είκοσι τρία~123 +χίλια διακόσια τριάντα τέσσερα~1234 +δύο χιλιάδες τριακόσια σαράντα πέντε~2345 +δέκα χιλιάδες~10000 +δεκαπέντε χιλιάδες~15000 +εκατό χιλιάδες~100000 +διακόσιες χιλιάδες~200000 +ένα εκατομμύριο~1000000 +δύο εκατομμύρια~2000000 +πέντε εκατομμύρια~5000000 +δέκα εκατομμύρια~10000000 +μηδέν~μηδέν +δύο~δύο +τρία~τρία +πέντε~πέντε diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_date.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_date.txt new file mode 100644 index 000000000..0c6df1236 --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_date.txt @@ -0,0 +1,8 @@ +είκοσι πέντε Δεκεμβρίου~25 12 +δέκα Μαρτίου δύο χιλιάδες είκοσι τρία~10 03 2023 +πέντε Ιουνίου~5 06 +δεκαπέντε Ιανουαρίου~15 01 +δεκατέσσερις Ιουλίου~14 07 +δεκατέσσερεις Ιουλίου~14 07 +δεκατρείς Ιουλίου~13 07 +τριάντα Νοεμβρίου~30 11 diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_decimal.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_decimal.txt new file mode 100644 index 000000000..c85959f79 --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_decimal.txt @@ -0,0 +1,5 @@ +τρία κόμμα ένα τέσσερα~3,14 +μηδέν κόμμα πέντε~0,5 +δέκα κόμμα πέντε~10,5 +εκατό κόμμα είκοσι τρία~100,23 +χίλια κόμμα μηδέν μηδέν ένα~1000,001 diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_electronic.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_electronic.txt new file mode 100644 index 000000000..bc9fd94d6 --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_electronic.txt @@ -0,0 +1,2 @@ +papadopoulos at gmail dot com~papadopoulos@gmail.com +nemo at outlook dot com~nemo@outlook.com diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_fraction.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_fraction.txt new file mode 100644 index 000000000..23b50831d --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_fraction.txt @@ -0,0 +1,15 @@ +ένα δεύτερο~1/2 +ένα τρίτο~1/3 +ένα τέταρτο~1/4 +ένα πέμπτο~1/5 +ένα έκτο~1/6 +ένα έβδομο~1/7 +ένα όγδοο~1/8 +ένα ένατο~1/9 +τρία τέταρτα~3/4 +δύο τρίτα~2/3 +πέντε έκτα~5/6 +επτά όγδοα~7/8 +ένα δέκατο~1/10 +τρία εικοστά~3/20 +ένα εικοστό~1/20 diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_measure.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_measure.txt new file mode 100644 index 000000000..adbdda900 --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_measure.txt @@ -0,0 +1,6 @@ +δέκα μέτρα~10 m +πέντε κιλά~5 kg +δύο γραμμάρια~2 g +ένα μέτρο~1 m +δέκα χιλιόμετρα~10 km +πέντε λίτρα~5 l diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_money.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_money.txt new file mode 100644 index 000000000..903f0bde3 --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_money.txt @@ -0,0 +1,4 @@ +πέντε ευρώ~€5 +δέκα δολάρια~$10 +ένα δολάριο~$1 +δέκα λίρες~£10 diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_ordinal.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_ordinal.txt new file mode 100644 index 000000000..afec3f9df --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_ordinal.txt @@ -0,0 +1,8 @@ +εικοστή πρώτη~21 +εικοστός πρώτος~21 +εικοστή~20 +εικοστός~20 +τριακοστή τρίτη~33 +τριακοστός τρίτος~33 +πεντηκοστή~50 +πεντηκοστός~50 diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_telephone.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_telephone.txt new file mode 100644 index 000000000..2feb671ae --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_telephone.txt @@ -0,0 +1,2 @@ +μηδέν ένα δύο τρία τέσσερα πέντε έξι εφτά οχτώ εννέα~0123456789 +μηδέν έξι εννέα επτά πέντε τρία οκτώ ένα δύο τέσσερα~0697538124 diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_time.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_time.txt new file mode 100644 index 000000000..94142b1c9 --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_time.txt @@ -0,0 +1,6 @@ +δύο τριάντα~02:30 +δέκα πέντε~10:05 +δώδεκα μηδέν~12:00 +τρεις τέσσερα~03:04 +έξι τριάντα~06:30 +είκοσι τρεις πενήντα εννέα~23:59 diff --git a/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_word.txt b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_word.txt new file mode 100644 index 000000000..34220138d --- /dev/null +++ b/tests/nemo_text_processing/el/data_inverse_text_normalization/test_cases_word.txt @@ -0,0 +1,4 @@ +δύο~δύο +τρία~τρία +πέντε~πέντε +μηδέν~μηδέν diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_cardinal.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_cardinal.txt new file mode 100644 index 000000000..9b59c2c3e --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_cardinal.txt @@ -0,0 +1,8 @@ +0~μηδέν +123~εκατόν είκοσι τρία +901~εννιακόσια ένα +1001~χίλια ένα +21~είκοσι ένα +100~εκατό +1000~χίλια +1000000~ένα εκατομμύριο diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_date.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_date.txt new file mode 100644 index 000000000..269bdd790 --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_date.txt @@ -0,0 +1,7 @@ +01/01~ένα Ιανουαρίου +25/12/2024~είκοσι πέντε Δεκεμβρίου δύο χιλιάδες είκοσι τέσσερα +2024~δύο χιλιάδες είκοσι τέσσερα +07/04~εφτά Απριλίου +03/03~τρία Μαρτίου +14/07~δεκατέσσερα Ιουλίου +25/12~είκοσι πέντε Δεκεμβρίου diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_decimal.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_decimal.txt new file mode 100644 index 000000000..1d6d38453 --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_decimal.txt @@ -0,0 +1,3 @@ +3,14~τρία κόμμα ένα τέσσερα +7,5~εφτά κόμμα πέντε +0,5~μηδέν κόμμα πέντε diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_electronic.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_electronic.txt new file mode 100644 index 000000000..526d3ade1 --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_electronic.txt @@ -0,0 +1 @@ +user@example.com~u s e r στο e x a m p l e τελεία c o m diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_fraction.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_fraction.txt new file mode 100644 index 000000000..c3801560a --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_fraction.txt @@ -0,0 +1,4 @@ +1/2~ένα δεύτερο +3/4~τρία τέταρτα +1/3~ένα τρίτο +2/5~δύο πέμπτα diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_measure.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_measure.txt new file mode 100644 index 000000000..4ea428dcb --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_measure.txt @@ -0,0 +1,5 @@ +5 μ~πέντε μέτρα +3 kg~τρία κιλά +100 g~εκατό γραμμάρια +2 l~δύο λίτρα +10 km~δέκα χιλιόμετρα diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_money.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_money.txt new file mode 100644 index 000000000..62b5c8b67 --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_money.txt @@ -0,0 +1,8 @@ +€5~πέντε ευρώ +5€~πέντε ευρώ +€0,50~μηδέν ευρώ πενήντα λεπτά +€1,50~ένα ευρώ πενήντα λεπτά +€2,50~δύο ευρώ πενήντα λεπτά +€3,00~τρία ευρώ μηδέν λεπτά +€0,01~μηδέν ευρώ ένα λεπτό +£1~μία λίρα diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_ordinal.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_ordinal.txt new file mode 100644 index 000000000..32e30fcc5 --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_ordinal.txt @@ -0,0 +1,7 @@ +21η~εικοστή πρώτη +1ος~πρώτος +10η~δέκατη +3η~τρίτη +2ο~δεύτερο +7η~έβδομη +7ος~έβδομος diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_range.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_range.txt new file mode 100644 index 000000000..26d38e8f4 --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_range.txt @@ -0,0 +1 @@ +100+~εκατό συν diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_roman.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_roman.txt new file mode 100644 index 000000000..66bb7f44f --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_roman.txt @@ -0,0 +1,8 @@ +IV~τέσσερα +VI~έξι +IX~εννιά +XI~έντεκα +XIII~δεκατρία +Κεφάλαιο V~Κεφάλαιο πέντε +Μέρος II~Μέρος δύο +Κεφάλαιο IV~Κεφάλαιο τέσσερα diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_serial.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_serial.txt new file mode 100644 index 000000000..141b27cd7 --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_serial.txt @@ -0,0 +1,10 @@ +a320~άλφα τρία δύο μηδέν +AB12~άλφαβήτα δώδεκα~άλφα βήτα δώδεκα +h800~έιτς οχτακόσια +b500~βήτα πεντακόσια +x100~χι εκατό +a1~άλφα ένα +c700~γάμα εφτακόσια +d600~δέλτα εξακόσια +A320~άλφα τρία δύο μηδέν +a100~άλφα εκατό diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_telephone.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_telephone.txt new file mode 100644 index 000000000..cbd04220f --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_telephone.txt @@ -0,0 +1,4 @@ +21-01-23~δύο ένα μηδέν ένα δύο τρία +210-1234567~δύο ένα μηδέν ένα δύο τρία τέσσερα πέντε έξι εφτά +698-123-4567~έξι εννιά οχτώ ένα δύο τρία τέσσερα πέντε έξι εφτά +210.1234567~δύο ένα μηδέν ένα δύο τρία τέσσερα πέντε έξι εφτά diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_time.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_time.txt new file mode 100644 index 000000000..a4ab4ebbb --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_time.txt @@ -0,0 +1,5 @@ +14:30~δεκατέσσερα τριάντα +09:05~εννιά πέντε +00:00~μηδέν μηδέν +12:00~δώδεκα μηδέν +08:15~οχτώ δεκαπέντε diff --git a/tests/nemo_text_processing/el/data_text_normalization/test_cases_whitelist.txt b/tests/nemo_text_processing/el/data_text_normalization/test_cases_whitelist.txt new file mode 100644 index 000000000..35adec4f0 --- /dev/null +++ b/tests/nemo_text_processing/el/data_text_normalization/test_cases_whitelist.txt @@ -0,0 +1,5 @@ +κ.λπ.~και τα λοιπά +κ.ά.~και άλλα +π.χ.~παραδείγματος χάριν +δηλ.~δηλαδή +τηλ.~τηλέφωνο diff --git a/tests/nemo_text_processing/el/test_cardinal.py b/tests/nemo_text_processing/el/test_cardinal.py new file mode 100644 index 000000000..f91c9ad52 --- /dev/null +++ b/tests/nemo_text_processing/el/test_cardinal.py @@ -0,0 +1,31 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from parameterized import parameterized + +from nemo_text_processing.text_normalization.normalize import Normalizer + +from ..utils import CACHE_DIR, parse_test_case_file + + +class TestCardinal: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_cardinal.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected diff --git a/tests/nemo_text_processing/el/test_itn.py b/tests/nemo_text_processing/el/test_itn.py new file mode 100644 index 000000000..3224dd494 --- /dev/null +++ b/tests/nemo_text_processing/el/test_itn.py @@ -0,0 +1,141 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from parameterized import parameterized + +from nemo_text_processing.inverse_text_normalization.inverse_normalize import InverseNormalizer + +from ..utils import CACHE_DIR, parse_test_case_file + + +class TestCardinal: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_cardinal.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected + + +class TestOrdinal: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_ordinal.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected + + +class TestDecimal: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_decimal.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected + + +class TestTime: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_time.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected + + +class TestDate: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_date.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected + + +class TestMeasure: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_measure.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected + + +class TestMoney: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_money.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected + + +class TestElectronic: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_electronic.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected + + +class TestTelephone: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_telephone.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected + + +class TestFraction: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_fraction.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected + + +class TestWord: + inverse_normalizer = InverseNormalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('el/data_inverse_text_normalization/test_cases_word.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_denorm(self, test_input, expected): + pred = self.inverse_normalizer.inverse_normalize(test_input, verbose=False) + assert pred == expected diff --git a/tests/nemo_text_processing/el/test_sparrowhawk_inverse_text_normalization.sh b/tests/nemo_text_processing/el/test_sparrowhawk_inverse_text_normalization.sh new file mode 100755 index 000000000..9d53b7f09 --- /dev/null +++ b/tests/nemo_text_processing/el/test_sparrowhawk_inverse_text_normalization.sh @@ -0,0 +1,81 @@ +#! /bin/sh + +GRAMMARS_DIR=${1:-"/workspace/sparrowhawk/documentation/grammars"} +PROJECT_DIR=${2:-"/workspace/tests/"} + +runtest () { + input=$1 + echo "INPUT is $input" + cd ${GRAMMARS_DIR} + + # read test file + while read testcase; do + IFS='~' read spoken written <<< $testcase + denorm_pred=$(echo $spoken | normalizer_main --config=sparrowhawk_configuration.ascii_proto 2>&1 | tail -n 1) + + # trim white space + written="$(echo -e "${written}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + denorm_pred="$(echo -e "${denorm_pred}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + + # input expected actual + assertEquals "$spoken" "$written" "$denorm_pred" + done < "$input" +} + +testITNCardinal() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_cardinal.txt + runtest $input +} + +testITNDate() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_date.txt + runtest $input +} + +testITNDecimal() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_decimal.txt + runtest $input +} + +testITNElectronic() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_electronic.txt + runtest $input +} + +testITNFraction() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_fraction.txt + runtest $input +} + +testITNMeasure() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_measure.txt + runtest $input +} + +testITNMoney() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_money.txt + runtest $input +} + +testITNOrdinal() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_ordinal.txt + runtest $input +} + +testITNTelephone() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_telephone.txt + runtest $input +} + +testITNTime() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_time.txt + runtest $input +} + +testITNWord() { + input=$PROJECT_DIR/el/data_inverse_text_normalization/test_cases_word.txt + runtest $input +} + +# Load shUnit2 +. $PROJECT_DIR/../shunit2/shunit2 diff --git a/tests/nemo_text_processing/el/test_sparrowhawk_normalization.sh b/tests/nemo_text_processing/el/test_sparrowhawk_normalization.sh new file mode 100755 index 000000000..bd10270e2 --- /dev/null +++ b/tests/nemo_text_processing/el/test_sparrowhawk_normalization.sh @@ -0,0 +1,101 @@ +#! /bin/sh + +GRAMMARS_DIR=${1:-"/workspace/sparrowhawk/documentation/grammars"} +PROJECT_DIR=${2:-"/workspace/tests/"} + +runtest () { + input=$1 + echo "INPUT is $input" + cd ${GRAMMARS_DIR} + + # read test file + while read testcase; do + IFS='~' read -a testcase_tokenized <<< $testcase + written=${testcase_tokenized[0]} + # only tests against first possible option when there are multiple shortest paths + spoken=${testcase_tokenized[1]} + + # replace non breaking space with breaking space + denorm_pred=$(echo $written | normalizer_main --config=sparrowhawk_configuration.ascii_proto 2>&1 | tail -n 1 | sed 's/\xC2\xA0/ /g') + + # trim white space + spoken="$(echo -e "${spoken}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + denorm_pred="$(echo -e "${denorm_pred}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + + # input expected actual + assertEquals "$written" "$spoken" "$denorm_pred" + done < "$input" +} + +testTNCardinal() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_cardinal.txt + runtest $input +} + +testTNDate() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_date.txt + runtest $input +} + +testTNDecimal() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_decimal.txt + runtest $input +} + +testTNElectronic() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_electronic.txt + runtest $input +} + +testTNFraction() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_fraction.txt + runtest $input +} + +testTNMeasure() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_measure.txt + runtest $input +} + +testTNMoney() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_money.txt + runtest $input +} + +testTNOrdinal() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_ordinal.txt + runtest $input +} + +testTNRange() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_range.txt + runtest $input +} + +testTNRoman() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_roman.txt + runtest $input +} + +testTNSerial() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_serial.txt + runtest $input +} + +testTNTelephone() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_telephone.txt + runtest $input +} + +testTNTime() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_time.txt + runtest $input +} + +testTNWhitelist() { + input=$PROJECT_DIR/el/data_text_normalization/test_cases_whitelist.txt + runtest $input +} + +# Load shUnit2 +. $PROJECT_DIR/../shunit2/shunit2 diff --git a/tests/nemo_text_processing/el/test_tn.py b/tests/nemo_text_processing/el/test_tn.py new file mode 100644 index 000000000..63e03e490 --- /dev/null +++ b/tests/nemo_text_processing/el/test_tn.py @@ -0,0 +1,175 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from parameterized import parameterized + +from nemo_text_processing.text_normalization.normalize import Normalizer +from nemo_text_processing.text_normalization.token_parser import TokenParser + +from ..utils import CACHE_DIR, parse_test_case_file + + +class TestCardinal: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_cardinal.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestOrdinal: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_ordinal.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestDecimal: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_decimal.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestFraction: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_fraction.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestMoney: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_money.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestDate: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_date.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestTime: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_time.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestMeasure: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_measure.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestTelephone: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_telephone.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestElectronic: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_electronic.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestWhitelist: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_whitelist.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestSerial: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_serial.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestRange: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_range.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected + + +class TestRoman: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_roman.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected diff --git a/tests/nemo_text_processing/el/test_whitelist.py b/tests/nemo_text_processing/el/test_whitelist.py new file mode 100644 index 000000000..5ec7be206 --- /dev/null +++ b/tests/nemo_text_processing/el/test_whitelist.py @@ -0,0 +1,31 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from parameterized import parameterized + +from nemo_text_processing.text_normalization.normalize import Normalizer + +from ..utils import CACHE_DIR, parse_test_case_file + + +class TestWhitelist: + normalizer = Normalizer(lang='el', cache_dir=CACHE_DIR, overwrite_cache=False, input_case='lower_cased') + + @parameterized.expand(parse_test_case_file('el/data_text_normalization/test_cases_whitelist.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred in expected diff --git a/tools/text_processing_deployment/GREEK_TN_ITN_GUIDE.md b/tools/text_processing_deployment/GREEK_TN_ITN_GUIDE.md new file mode 100644 index 000000000..d1c2c9690 --- /dev/null +++ b/tools/text_processing_deployment/GREEK_TN_ITN_GUIDE.md @@ -0,0 +1,227 @@ +# Greek (el) Text Normalization & Inverse Text Normalization — Usage & Deployment + +This guide covers using the Greek TN/ITN grammars from Python and deploying them as a +C++ [Sparrowhawk](https://github.com/google/sparrowhawk) service on a **Linux x86_64** box. + +- **TN (Text Normalization)** turns written text into its spoken form, for TTS + (`25/12/2024` → `είκοσι πέντε Δεκεμβρίου δύο χιλιάδες είκοσι τέσσερα`). +- **ITN (Inverse Text Normalization)** turns spoken/ASR output into written form + (`δεκατέσσερα Ιουλίου` → `14 Ιουλίου`, `δέκα μέτρα` → `10 m`). + +--- + +## 1. Prerequisites + +- Linux x86_64 (native — no emulation needed; the Sparrowhawk image installs `thrax`, + which conda-forge only ships for `x86_64`/`linux-64`, **not** arm64). +- [Miniconda/Anaconda](https://docs.conda.io/en/latest/miniconda.html). +- For the C++ deployment: Docker Engine (`docker` + a running daemon). + +--- + +## 2. Install the Python package (from this repo) + +The Greek grammars ship with this source tree, so install from source (not the PyPI wheel): + +```bash +git clone NeMo-text-processing +cd NeMo-text-processing +git checkout feat/greek-tn-itn # or the branch/tag that contains Greek + +conda create -y -n nemo_tn python=3.10 +conda activate nemo_tn +conda install -y -c conda-forge pynini=2.1.6.post1 +pip install -e . +``` + +Verify: + +```bash +python -c "from nemo_text_processing.text_normalization.normalize import Normalizer; \ +print(Normalizer(lang='el', input_case='cased').normalize('3,14 και 21ος', verbose=False))" +# -> τρία κόμμα δεκατέσσερα και εικοστός πρώτος +``` + +--- + +## 3. Use it from Python + +### Text normalization (for TTS) + +```python +from nemo_text_processing.text_normalization.normalize import Normalizer + +# input_case='cased' -> keeps capitalization (recommended for real text) +# input_case='lower_cased' -> assume input is already lowercased +# cache_dir caches compiled grammars as .far (first build ~60s, then instant) +tn = Normalizer(lang='el', input_case='cased', cache_dir='el_grammars') + +tn.normalize('Στις 25/12/2024 πλήρωσα 15,50€', verbose=False) +# 'Στις είκοσι πέντε Δεκεμβρίου δύο χιλιάδες είκοσι τέσσερα πλήρωσα δεκαπέντε ευρώ και πενήντα λεπτά' +``` + +### Inverse text normalization (for ASR output) + +```python +from nemo_text_processing.inverse_text_normalization.inverse_normalize import InverseNormalizer + +itn = InverseNormalizer(lang='el', cache_dir='el_grammars') +itn.inverse_normalize('δέκα μέτρα', verbose=False) # '10 m' +``` + +> **Cache invalidation:** after editing any grammar, delete `cache_dir` or pass +> `overwrite_cache=True`, otherwise the stale `.far` is reused. + +--- + +## 4. Use it from the command line + +```bash +# TN — single string +python nemo_text_processing/text_normalization/normalize.py \ + --language el --input_case cased --text "3,14 και 21ος" + +# TN — a file (one sentence per line), parallelized +python nemo_text_processing/text_normalization/normalize.py \ + --language el --input_case cased --input_file in.txt --output_file out.txt --n_jobs 8 + +# ITN +python nemo_text_processing/inverse_text_normalization/inverse_normalize.py \ + --language el --text "δεκατέσσερα Ιουλίου" +``` + +--- + +## 5. Run the test suite (optional) + +```bash +python -m pytest tests/nemo_text_processing/el/ -m "not pleasefixme" --cpu -q +``` + +--- + +## 6. Deploy as a Sparrowhawk C++ service + +Sparrowhawk runs the compiled `.far` grammars in C++ without a Python runtime — suitable for +production serving. Everything below runs from `tools/text_processing_deployment/`. + +```bash +cd tools/text_processing_deployment +``` + +### 6.1 Export the grammars (.far) + +The exported grammars are already committed under +`el_tn_grammars_cased/` and `el_itn_grammars_cased/` (each with `classify/tokenize_and_classify.far` +and `verbalize/verbalize.far`). To regenerate them: + +```bash +# TN +bash export_grammars.sh --GRAMMARS=tn_grammars --LANGUAGE=el --INPUT_CASE=cased --MODE=export +# ITN +bash export_grammars.sh --GRAMMARS=itn_grammars --LANGUAGE=el --INPUT_CASE=cased --MODE=export +``` + +This calls `pynini_export.py` (which has an `el` branch registered) and writes +`__/{classify,verbalize}/*.far`. + +> If you run `pynini_export.py` directly instead of via the script, make sure +> `nemo_text_processing` is importable (`pip install -e .`, or prefix with +> `PYTHONPATH=`). + +### 6.2 Build the Sparrowhawk Docker image + +On native Linux x86_64 the image builds cleanly (compiles OpenFst, re2, thrax, protobuf and +Sparrowhawk from source — first build takes a while): + +```bash +bash docker/build.sh # produces image sparrowhawk:latest +``` + +### 6.3 Interactive shell (smoke test) + +`export_grammars.sh --MODE=interactive` builds the image (if needed), mounts the grammars, and +drops you into the container: + +```bash +# Text normalization +bash export_grammars.sh --GRAMMARS=tn_grammars --LANGUAGE=el --INPUT_CASE=cased --MODE=interactive +# inside the container: +echo "Στις 25/12/2024 πλήρωσα 15,50€" | \ + normalizer_main --config=sparrowhawk_configuration.ascii_proto + +# Inverse text normalization +bash export_grammars.sh --GRAMMARS=itn_grammars --LANGUAGE=el --INPUT_CASE=cased --MODE=interactive +# inside the container: +echo "δεκατέσσερα Ιουλίου" | \ + normalizer_main --config=sparrowhawk_configuration.ascii_proto +``` + +`normalizer_main` reads one input per line from **stdin** and writes the normalized result to +**stdout** — this is the entry point you wrap for serving. + +### 6.4 Run the grammar test suite in the container + +```bash +bash export_grammars.sh --GRAMMARS=tn_grammars --LANGUAGE=el --INPUT_CASE=cased --MODE=test +bash export_grammars.sh --GRAMMARS=itn_grammars --LANGUAGE=el --INPUT_CASE=cased --MODE=test +``` + +These run `tests/nemo_text_processing/el/test_sparrowhawk_{normalization,inverse_text_normalization}.sh` +inside the container against the committed test cases. + +### 6.5 How the grammars are wired + +`docker/launch.sh` mounts the two `.far` directories into the Sparrowhawk grammar path: + +``` +__/classify -> /workspace/sparrowhawk/documentation/grammars/en_toy/classify +__/verbalize -> /workspace/sparrowhawk/documentation/grammars/en_toy/verbalize +``` + +`sparrowhawk_configuration.ascii_proto` (in `documentation/grammars/`) points at +`classify/tokenize_and_classify.far` and `verbalize/verbalize.far`. To serve TN and ITN +simultaneously, run two containers (or two config/grammar dirs) — one per direction — since each +config points at a single classify+verbalize pair. + +### 6.6 Serving pattern + +For a long-running service, start the container with the grammars mounted and keep +`normalizer_main` fed via a socket/HTTP wrapper of your choice, e.g.: + +```bash +docker run -i --rm \ + -v "$PWD/el_tn_grammars_cased/classify:/workspace/sparrowhawk/documentation/grammars/en_toy/classify" \ + -v "$PWD/el_tn_grammars_cased/verbalize:/workspace/sparrowhawk/documentation/grammars/en_toy/verbalize" \ + -w /workspace/sparrowhawk/documentation/grammars \ + sparrowhawk:latest \ + normalizer_main --config=sparrowhawk_configuration.ascii_proto +``` + +Then pipe requests to the container's stdin (one utterance per line). Wrap this with a small +server process (gRPC/HTTP) that owns the stdin/stdout pipes for a production endpoint. + +--- + +## 7. Greek-specific behavior notes + +- **Numbers** render in the neuter citation form; the thousands multiplier is feminine + (`3000` → `τρεις χιλιάδες`); millions/billions stay neuter. Range: 0–999,999,999,999. +- **Ordinals** inflect for full gender/case (`1ος`→`πρώτος`, `21η`→`εικοστή πρώτη`, `3ου`→`τρίτου`). +- **Measures (ITN)** output SI symbols as the canonical form (`δέκα μέτρα` → `10 m`). +- **Acronyms** are handled **only via the whitelist**. There is deliberately no generic + all-caps letter-splitter: Greek acronym pronunciation is irregular and not derivable from + spelling (ΕΡΤ→"ερτ", ΚΚΕ→"κου-κου-ε", ΕΥΔΑΠ→"εϊδάπ"), and a blanket splitter would mangle + ordinary all-caps words (ΕΛΛΑΔΑ, ΙΝΤΡΑΚΟΜ). Add known acronyms with their correct spoken form + to the whitelist TSV. + +--- + +## 8. Troubleshooting + +| Symptom | Cause / Fix | +|---|---| +| `ModuleNotFoundError: nemo_text_processing` when exporting | Package not importable from the tools dir. `pip install -e .` or prefix with `PYTHONPATH=`. | +| Docker build fails on `conda install thrax=1.3.4` with `PackagesNotFoundError` / `linux-aarch64` | You are on arm64 (e.g. Apple Silicon). Build on x86_64, or force `--platform linux/amd64` (slow QEMU emulation). | +| Grammar changes not taking effect | Stale cache. Delete `cache_dir` / the `*_grammars_*` dir, or pass `--overwrite_cache` / `overwrite_cache=True`. | +| Want TN and ITN from one endpoint | Run two containers/configs; each Sparrowhawk config serves a single classify+verbalize pair. | diff --git a/tools/text_processing_deployment/el_itn_grammars_cased/classify/tokenize_and_classify.far b/tools/text_processing_deployment/el_itn_grammars_cased/classify/tokenize_and_classify.far new file mode 100644 index 000000000..b82a9821f Binary files /dev/null and b/tools/text_processing_deployment/el_itn_grammars_cased/classify/tokenize_and_classify.far differ diff --git a/tools/text_processing_deployment/el_itn_grammars_cased/verbalize/verbalize.far b/tools/text_processing_deployment/el_itn_grammars_cased/verbalize/verbalize.far new file mode 100644 index 000000000..781be5481 Binary files /dev/null and b/tools/text_processing_deployment/el_itn_grammars_cased/verbalize/verbalize.far differ diff --git a/tools/text_processing_deployment/el_tn_grammars_cased/classify/tokenize_and_classify.far b/tools/text_processing_deployment/el_tn_grammars_cased/classify/tokenize_and_classify.far new file mode 100644 index 000000000..d3931d1dc Binary files /dev/null and b/tools/text_processing_deployment/el_tn_grammars_cased/classify/tokenize_and_classify.far differ diff --git a/tools/text_processing_deployment/el_tn_grammars_cased/verbalize/verbalize.far b/tools/text_processing_deployment/el_tn_grammars_cased/verbalize/verbalize.far new file mode 100644 index 000000000..a641984de Binary files /dev/null and b/tools/text_processing_deployment/el_tn_grammars_cased/verbalize/verbalize.far differ diff --git a/tools/text_processing_deployment/pynini_export.py b/tools/text_processing_deployment/pynini_export.py index 73a4fc138..aa067f030 100644 --- a/tools/text_processing_deployment/pynini_export.py +++ b/tools/text_processing_deployment/pynini_export.py @@ -109,6 +109,7 @@ def parse_args(): 'ja', 'rw', 'ko', + 'el', ], type=str, default='en', @@ -352,6 +353,17 @@ def parse_args(): ClassifyFst as TNClassifyFst, ) from nemo_text_processing.text_normalization.ko.verbalizers.verbalize import VerbalizeFst as TNVerbalizeFst + elif args.language == 'el': + from nemo_text_processing.inverse_text_normalization.el.taggers.tokenize_and_classify import ( + ClassifyFst as ITNClassifyFst, + ) + from nemo_text_processing.inverse_text_normalization.el.verbalizers.verbalize import ( + VerbalizeFst as ITNVerbalizeFst, + ) + from nemo_text_processing.text_normalization.el.taggers.tokenize_and_classify import ( + ClassifyFst as TNClassifyFst, + ) + from nemo_text_processing.text_normalization.el.verbalizers.verbalize import VerbalizeFst as TNVerbalizeFst else: raise KeyError(f"Language {args.language} is not defined for export.") output_dir = os.path.join(args.output_dir, f"{args.language}_{args.grammars}_{args.input_case}") diff --git a/tools/text_processing_deployment/server/Dockerfile b/tools/text_processing_deployment/server/Dockerfile new file mode 100644 index 000000000..24d7a4ca1 --- /dev/null +++ b/tools/text_processing_deployment/server/Dockerfile @@ -0,0 +1,59 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# HTTP server that serves NeMo TN/ITN grammars through Sparrowhawk. +# +# Build FROM the Sparrowhawk backend image produced by +# tools/text_processing_deployment/docker/build.sh (tag: sparrowhawk:latest), +# which already provides the `normalizer_main` binary and the FST runtime. +# +# The build context is tools/text_processing_deployment/ so the exported grammar +# dirs (el_*_grammars_cased) are visible to COPY. Build with: +# docker build -f server/Dockerfile -t nemo-sparrowhawk-server \ +# --build-arg GRAMMARS_TN_SRC=el_tn_grammars_cased \ +# --build-arg GRAMMARS_ITN_SRC=el_itn_grammars_cased . +ARG BASE_IMAGE=sparrowhawk:latest +FROM ${BASE_IMAGE} + +# Grammar source dirs, relative to the build context. Point these at whatever +# language you exported (default: Greek, cased). +ARG GRAMMARS_TN_SRC=el_tn_grammars_cased +ARG GRAMMARS_ITN_SRC=el_itn_grammars_cased + +WORKDIR /app + +# The base is continuumio/miniconda3, so `pip` is already on PATH. +COPY server/requirements.txt /app/requirements.txt +RUN pip install --no-cache-dir -r /app/requirements.txt + +# Server code + Sparrowhawk ascii_proto config templates. +COPY server/app.py server/normalizer.py /app/ +COPY server/configs /app/configs +COPY server/entrypoint.sh /app/entrypoint.sh +RUN chmod +x /app/entrypoint.sh + +# Bake the compiled .far grammars into the image. Override at runtime by bind +# mounting over /app/grammars/{tn,itn} or setting GRAMMARS_TN / GRAMMARS_ITN. +COPY ${GRAMMARS_TN_SRC} /app/grammars/tn +COPY ${GRAMMARS_ITN_SRC} /app/grammars/itn + +ENV LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + LANGUAGE=el \ + PORT=8000 \ + WORKERS_PER_DIRECTION=2 \ + STDBUF_PREFIX="stdbuf -oL -eL" + +EXPOSE 8000 +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/tools/text_processing_deployment/server/README.md b/tools/text_processing_deployment/server/README.md new file mode 100644 index 000000000..f73288854 --- /dev/null +++ b/tools/text_processing_deployment/server/README.md @@ -0,0 +1,146 @@ +# Sparrowhawk TN/ITN HTTP server + +A small FastAPI service that serves **Text Normalization (TN)** and **Inverse Text +Normalization (ITN)** through Google [Sparrowhawk](https://github.com/google/sparrowhawk) — +the C++ backend that runs the compiled `.far` WFST grammars exported from +`nemo_text_processing`, no Python grammar runtime required. + +One container serves **both directions** by running two persistent +`normalizer_main` processes (one for TN, one for ITN), each behind a small worker +pool, wrapped by a JSON HTTP API. + +``` + ┌──────────────────────── nemo-sparrowhawk-server ───────────────────────┐ + HTTP ─────▶│ FastAPI (app.py) │ + :8000 │ ├─ /tn ─▶ EnginePool("tn") ─▶ normalizer_main (TN grammars) x N │ + │ └─ /itn ─▶ EnginePool("itn") ─▶ normalizer_main (ITN grammars) x N │ + └──────────────────────────────────────────────────────────────────────────┘ +``` + +## Endpoints + +| Method | Path | Body | Returns | +|--------|--------------|-------------------------------------------------|---------| +| GET | `/health` | — | `{"status","engines":{"tn","itn"}}` | +| GET | `/` | — | service metadata | +| POST | `/tn` | `{"text":"..."}` or `{"texts":[...]}` | `{"direction":"tn","results":[...]}` | +| POST | `/itn` | `{"text":"..."}` or `{"texts":[...]}` | `{"direction":"itn","results":[...]}` | +| POST | `/normalize` | `{"direction":"tn"\|"itn","text":"..."}` | `{"direction","results":[...]}` | + +## Quick start (Docker) + +Runs from `tools/text_processing_deployment/` (the Docker build context). Requires +the exported grammars — Greek cased ones are already committed as +`el_tn_grammars_cased/` and `el_itn_grammars_cased/`. + +```bash +cd tools/text_processing_deployment + +# Builds the sparrowhawk:latest base image if missing, then the server image, +# then runs it on :8000. Add EXPORT=1 to re-export the grammars first. +bash server/build_and_run.sh +``` + +Then: + +```bash +curl -s localhost:8000/health + +curl -s localhost:8000/tn -H 'content-type: application/json' \ + -d '{"text":"Στις 25/12/2024 πλήρωσα 15,50€"}' +# {"direction":"tn","results":["Στις είκοσι πέντε Δεκεμβρίου δύο χιλιάδες είκοσι τέσσερα πλήρωσα δεκαπέντε ευρώ και πενήντα λεπτά"]} + +curl -s localhost:8000/itn -H 'content-type: application/json' \ + -d '{"text":"δεκατέσσερις Ιουλίου"}' +# {"direction":"itn","results":["14 Ιουλίου"]} + +# batch +curl -s localhost:8000/normalize -H 'content-type: application/json' \ + -d '{"direction":"itn","texts":["δέκα μέτρα","δεκατέσσερις Ιουλίου"]}' +``` + +## Building manually + +```bash +cd tools/text_processing_deployment + +# 1. base Sparrowhawk image (slow first time; needs Linux x86_64 — see the guide) +bash docker/build.sh # -> sparrowhawk:latest + +# 2. server image (context = this dir so it can COPY the grammar dirs) +docker build -f server/Dockerfile -t nemo-sparrowhawk-server \ + --build-arg GRAMMARS_TN_SRC=el_tn_grammars_cased \ + --build-arg GRAMMARS_ITN_SRC=el_itn_grammars_cased . + +# 3. run +docker run --rm -p 8000:8000 nemo-sparrowhawk-server +``` + +### Serving a different language + +Export the grammars for your language, then point the build args at them: + +```bash +bash export_grammars.sh --GRAMMARS=tn_grammars --LANGUAGE=de --INPUT_CASE=cased --MODE=export +bash export_grammars.sh --GRAMMARS=itn_grammars --LANGUAGE=de --INPUT_CASE=cased --MODE=export +docker build -f server/Dockerfile -t nemo-sparrowhawk-de \ + --build-arg GRAMMARS_TN_SRC=de_tn_grammars_cased \ + --build-arg GRAMMARS_ITN_SRC=de_itn_grammars_cased . +``` + +To swap grammars **without rebuilding**, bind-mount over the baked-in dirs: + +```bash +docker run --rm -p 8000:8000 \ + -v "$PWD/de_tn_grammars_cased:/app/grammars/tn:ro" \ + -v "$PWD/de_itn_grammars_cased:/app/grammars/itn:ro" \ + nemo-sparrowhawk-server +``` + +If a direction's grammars are absent, that direction is simply disabled and its +endpoint returns `503`; the other keeps working. + +## Configuration (environment variables) + +| Var | Default | Meaning | +|-----|---------|---------| +| `PORT` / `HOST` | `8000` / `0.0.0.0` | HTTP bind | +| `WORKERS_PER_DIRECTION` | `2` | persistent `normalizer_main` processes per direction (throughput) | +| `IO_TIMEOUT` | `15` | seconds to wait for a normalized line before restarting the worker | +| `STDBUF_PREFIX` | `stdbuf -oL -eL` | forces line buffering on the child (see below); set empty to disable | +| `SPARROWHAWK_BIN` | `normalizer_main` | path to the binary | +| `SPARROWHAWK_CONFIG` | `sparrowhawk_configuration.ascii_proto` | config filename inside each serving dir | +| `GRAMMARS_TN` / `GRAMMARS_ITN` | `/app/grammars/{tn,itn}` | source of the `.far` grammars | +| `LOG_LEVEL` | `INFO` | set `DEBUG` to log the child's stderr | + +## Why `stdbuf -oL` + +`normalizer_main` writes results with C++ stdio, which is **block-buffered** when +stdout is a pipe. In a persistent request/response loop that means the single +result line is never flushed until the buffer fills — a deadlock. Launching the +child under `stdbuf -oL -eL` forces line buffering so each result is flushed +immediately. stderr is additionally drained on a background thread so diagnostics +can't fill the pipe, and so stdout carries only the normalized text (one line per +request). This matches the contract the repo's `test_sparrowhawk_*.sh` scripts +rely on (`echo … | normalizer_main … | tail -n 1`). + +## Local test (no Sparrowhawk image needed) + +`tests/test_local.sh` runs the full HTTP stack against `tests/fake_normalizer_main.py`, +a stand-in that honours the same stdin→stdout line contract. It validates the pipe +wrapper, the pool, and every endpoint on any platform: + +```bash +PYTHON=python3 bash server/tests/test_local.sh +``` + +## Notes + +* A single `normalizer_main` process is single-threaded; concurrency comes from + `WORKERS_PER_DIRECTION`. The FastAPI handler offloads the blocking pipe I/O to a + threadpool, and the pool's checkout queue provides backpressure. +* Keep `uvicorn --workers 1`: the engine pools live in-process, so extra uvicorn + workers would multiply the number of `normalizer_main` processes. Scale out with + container replicas behind a load balancer instead. +* Sparrowhawk (and thus this image) is **Linux x86_64** — see + [`GREEK_TN_ITN_GUIDE.md`](../GREEK_TN_ITN_GUIDE.md) §1/§8 for the platform caveat. diff --git a/tools/text_processing_deployment/server/app.py b/tools/text_processing_deployment/server/app.py new file mode 100644 index 000000000..da43c0dcf --- /dev/null +++ b/tools/text_processing_deployment/server/app.py @@ -0,0 +1,148 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""HTTP server exposing Sparrowhawk TN and ITN over a small JSON API. + +Endpoints +--------- +* ``GET /health`` liveness + which engines are up +* ``POST /tn`` {"text": "..."} | {"texts": [...]} -> normalized +* ``POST /itn`` {"text": "..."} | {"texts": [...]} -> normalized +* ``POST /normalize`` {"text": "...", "direction": "tn"|"itn"} +* ``GET /`` service metadata + +Directions are enabled when their grammar working directory exists (``TN_DIR`` / +``ITN_DIR``). Each direction is backed by a pool of persistent ``normalizer_main`` +processes (see :mod:`normalizer`). +""" + +import logging +import os +from contextlib import asynccontextmanager +from typing import Dict, List, Optional + +from fastapi import FastAPI, HTTPException +from fastapi.concurrency import run_in_threadpool +from normalizer import EnginePool, pool_from_env +from pydantic import BaseModel, Field + +logging.basicConfig( + level=os.environ.get("LOG_LEVEL", "INFO").upper(), + format="%(asctime)s %(levelname)s %(name)s: %(message)s", +) +log = logging.getLogger("server") + +TN_DIR = os.environ.get("TN_DIR", "/workspace/serve/tn") +ITN_DIR = os.environ.get("ITN_DIR", "/workspace/serve/itn") +LANGUAGE = os.environ.get("LANGUAGE", "el") + +ENGINES: Dict[str, EnginePool] = {} + + +def _config_present(direction_dir: str) -> bool: + return os.path.isfile( + os.path.join(direction_dir, os.environ.get("SPARROWHAWK_CONFIG", "sparrowhawk_configuration.ascii_proto")) + ) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Spin up an engine pool for each direction whose grammars are present. + for name, d in (("tn", TN_DIR), ("itn", ITN_DIR)): + if _config_present(d): + log.info("initializing '%s' engine from %s", name, d) + ENGINES[name] = pool_from_env(name, d) + else: + log.warning("skipping '%s': no config found under %s", name, d) + if not ENGINES: + log.error("no engines available — check TN_DIR/ITN_DIR and grammar export") + yield + for pool in ENGINES.values(): + pool.close() + + +app = FastAPI( + title="NeMo Sparrowhawk TN/ITN", + description="Text Normalization and Inverse Text Normalization served by Google Sparrowhawk.", + version="1.0.0", + lifespan=lifespan, +) + + +class NormalizeRequest(BaseModel): + text: Optional[str] = Field(default=None, description="Single input string.") + texts: Optional[List[str]] = Field(default=None, description="Batch of input strings.") + + +class DirectedRequest(NormalizeRequest): + direction: str = Field(description="'tn' (written->spoken) or 'itn' (spoken->written).") + + +class NormalizeResponse(BaseModel): + direction: str + results: List[str] + + +def _inputs(req: NormalizeRequest) -> List[str]: + if req.texts is not None: + return req.texts + if req.text is not None: + return [req.text] + raise HTTPException(status_code=422, detail="provide 'text' or 'texts'") + + +async def _run(direction: str, req: NormalizeRequest) -> NormalizeResponse: + pool = ENGINES.get(direction) + if pool is None: + raise HTTPException(status_code=503, detail=f"direction '{direction}' not available") + items = _inputs(req) + # Offload the blocking pipe I/O to the threadpool; the pool's internal queue + # bounds real concurrency to the number of worker processes. + results = [await run_in_threadpool(pool.normalize, t) for t in items] + return NormalizeResponse(direction=direction, results=results) + + +@app.get("/") +def root(): + return { + "service": "nemo-sparrowhawk-tn-itn", + "language": LANGUAGE, + "directions": sorted(ENGINES.keys()), + "endpoints": ["/health", "/tn", "/itn", "/normalize"], + } + + +@app.get("/health") +def health(): + status = {name: ("up" if pool.healthy() else "down") for name, pool in ENGINES.items()} + ok = bool(ENGINES) and all(v == "up" for v in status.values()) + return {"status": "ok" if ok else "degraded", "engines": status} + + +@app.post("/tn", response_model=NormalizeResponse) +async def tn(req: NormalizeRequest): + return await _run("tn", req) + + +@app.post("/itn", response_model=NormalizeResponse) +async def itn(req: NormalizeRequest): + return await _run("itn", req) + + +@app.post("/normalize", response_model=NormalizeResponse) +async def normalize(req: DirectedRequest): + direction = req.direction.lower() + if direction not in ("tn", "itn"): + raise HTTPException(status_code=422, detail="direction must be 'tn' or 'itn'") + return await _run(direction, req) diff --git a/tools/text_processing_deployment/server/build_and_run.sh b/tools/text_processing_deployment/server/build_and_run.sh new file mode 100755 index 000000000..0b1b2cbfd --- /dev/null +++ b/tools/text_processing_deployment/server/build_and_run.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# One-shot: (optionally) export grammars, ensure the Sparrowhawk base image +# exists, build the server image, and run it on :8000. +# +# Usage: +# bash server/build_and_run.sh # Greek, cased, reuse existing .far +# LANGUAGE=el INPUT_CASE=cased EXPORT=1 bash server/build_and_run.sh # re-export first +# +# Must be run from tools/text_processing_deployment/ (the build context). +set -euo pipefail + +LANGUAGE=${LANGUAGE:-el} +INPUT_CASE=${INPUT_CASE:-cased} +PORT=${PORT:-8000} +EXPORT=${EXPORT:-0} +IMAGE=${IMAGE:-nemo-sparrowhawk-server} +BASE_IMAGE=${BASE_IMAGE:-sparrowhawk:latest} + +SCRIPT_DIR=$(cd "$(dirname "$0")"; pwd) +CTX_DIR=$(cd "$SCRIPT_DIR/.."; pwd) # tools/text_processing_deployment +cd "$CTX_DIR" + +TN_SRC="${LANGUAGE}_tn_grammars_${INPUT_CASE}" +ITN_SRC="${LANGUAGE}_itn_grammars_${INPUT_CASE}" + +if [[ "$EXPORT" == "1" ]]; then + echo "[build] exporting grammars for $LANGUAGE ($INPUT_CASE)" + bash export_grammars.sh --GRAMMARS=tn_grammars --LANGUAGE="$LANGUAGE" --INPUT_CASE="$INPUT_CASE" --MODE=export + bash export_grammars.sh --GRAMMARS=itn_grammars --LANGUAGE="$LANGUAGE" --INPUT_CASE="$INPUT_CASE" --MODE=export +fi + +if [[ ! -f "$TN_SRC/classify/tokenize_and_classify.far" && ! -f "$ITN_SRC/classify/tokenize_and_classify.far" ]]; then + echo "[build] ERROR: no grammars found ($TN_SRC / $ITN_SRC). Run with EXPORT=1 or export first." >&2 + exit 1 +fi + +if ! docker image inspect "$BASE_IMAGE" >/dev/null 2>&1; then + echo "[build] base image $BASE_IMAGE missing — building it (this is slow the first time)" + bash docker/build.sh +fi + +echo "[build] building $IMAGE" +docker build -f server/Dockerfile -t "$IMAGE" \ + --build-arg BASE_IMAGE="$BASE_IMAGE" \ + --build-arg GRAMMARS_TN_SRC="$TN_SRC" \ + --build-arg GRAMMARS_ITN_SRC="$ITN_SRC" \ + . + +echo "[run] starting $IMAGE on :$PORT" +exec docker run --rm -it \ + -p "$PORT:8000" \ + --shm-size=1g \ + -e LANGUAGE="$LANGUAGE" \ + "$IMAGE" diff --git a/tools/text_processing_deployment/server/configs/sentence_boundary_exceptions.txt b/tools/text_processing_deployment/server/configs/sentence_boundary_exceptions.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tools/text_processing_deployment/server/configs/sparrowhawk_configuration.ascii_proto b/tools/text_processing_deployment/server/configs/sparrowhawk_configuration.ascii_proto new file mode 100644 index 000000000..47f76952d --- /dev/null +++ b/tools/text_processing_deployment/server/configs/sparrowhawk_configuration.ascii_proto @@ -0,0 +1,7 @@ +tokenizer_grammar: "tokenizer.ascii_proto" + +verbalizer_grammar: "verbalizer.ascii_proto" + +sentence_boundary_regexp: "[\\!] " + +sentence_boundary_exceptions_file: "sentence_boundary_exceptions.txt" diff --git a/tools/text_processing_deployment/server/configs/tokenizer.ascii_proto b/tools/text_processing_deployment/server/configs/tokenizer.ascii_proto new file mode 100644 index 000000000..e37758f96 --- /dev/null +++ b/tools/text_processing_deployment/server/configs/tokenizer.ascii_proto @@ -0,0 +1,5 @@ +grammar_file: "classify/tokenize_and_classify.far" + +grammar_name: "TokenizerClassifier" + +rules { main: "TOKENIZE_AND_CLASSIFY" } diff --git a/tools/text_processing_deployment/server/configs/verbalizer.ascii_proto b/tools/text_processing_deployment/server/configs/verbalizer.ascii_proto new file mode 100644 index 000000000..44d7008a2 --- /dev/null +++ b/tools/text_processing_deployment/server/configs/verbalizer.ascii_proto @@ -0,0 +1,5 @@ +grammar_file: "verbalize/verbalize.far" + +grammar_name: "Verbalizer" + +rules { main: "ALL" redup: "REDUP" } diff --git a/tools/text_processing_deployment/server/entrypoint.sh b/tools/text_processing_deployment/server/entrypoint.sh new file mode 100755 index 000000000..99d083d12 --- /dev/null +++ b/tools/text_processing_deployment/server/entrypoint.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Assembles a self-contained Sparrowhawk serving directory for each available +# direction, then starts the HTTP server. +# +# For each direction we lay out (relative paths matter — normalizer_main is run +# with cwd set to the direction dir): +# /sparrowhawk_configuration.ascii_proto +# /tokenizer.ascii_proto -> classify/tokenize_and_classify.far +# /verbalizer.ascii_proto -> verbalize/verbalize.far +# /sentence_boundary_exceptions.txt +# /classify/tokenize_and_classify.far +# /verbalize/verbalize.far +set -euo pipefail + +CONFIG_SRC=${CONFIG_SRC:-/app/configs} +SERVE_ROOT=${SERVE_ROOT:-/workspace/serve} + +# Where the exported .far grammars live. Baked into the image by default, but can +# be overridden at runtime with a bind mount (e.g. -e GRAMMARS_TN=/mnt/tn). +GRAMMARS_TN=${GRAMMARS_TN:-/app/grammars/tn} +GRAMMARS_ITN=${GRAMMARS_ITN:-/app/grammars/itn} + +export TN_DIR=${TN_DIR:-$SERVE_ROOT/tn} +export ITN_DIR=${ITN_DIR:-$SERVE_ROOT/itn} + +setup_direction () { + local src=$1 dst=$2 name=$3 + local classify="$src/classify/tokenize_and_classify.far" + local verbalize="$src/verbalize/verbalize.far" + if [[ ! -f "$classify" || ! -f "$verbalize" ]]; then + echo "[entrypoint] '$name': grammars not found under $src — direction disabled" + return + fi + echo "[entrypoint] '$name': wiring grammars from $src -> $dst" + mkdir -p "$dst/classify" "$dst/verbalize" + cp -f "$classify" "$dst/classify/tokenize_and_classify.far" + cp -f "$verbalize" "$dst/verbalize/verbalize.far" + cp -f "$CONFIG_SRC"/sparrowhawk_configuration.ascii_proto \ + "$CONFIG_SRC"/tokenizer.ascii_proto \ + "$CONFIG_SRC"/verbalizer.ascii_proto \ + "$CONFIG_SRC"/sentence_boundary_exceptions.txt \ + "$dst/" +} + +setup_direction "$GRAMMARS_TN" "$TN_DIR" "tn" +setup_direction "$GRAMMARS_ITN" "$ITN_DIR" "itn" + +exec uvicorn app:app \ + --host "${HOST:-0.0.0.0}" \ + --port "${PORT:-8000}" \ + --workers 1 diff --git a/tools/text_processing_deployment/server/normalizer.py b/tools/text_processing_deployment/server/normalizer.py new file mode 100644 index 000000000..30adf4283 --- /dev/null +++ b/tools/text_processing_deployment/server/normalizer.py @@ -0,0 +1,177 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Thin Python wrapper around Sparrowhawk's ``normalizer_main`` binary. + +``normalizer_main`` is an interactive C++ tool: it loads a pair of compiled WFST +grammars (``classify/tokenize_and_classify.far`` + ``verbalize/verbalize.far``) +once at start-up, then reads one utterance per line from *stdin* and writes the +normalized result — one line — to *stdout*. + +We keep the process alive so the (slow) grammar load happens only once, and talk +to it over its stdin/stdout pipes. Two things make this reliable: + +* **Line buffering.** A C++ program writing to a pipe is block-buffered by + default, so its single line of output would never be flushed until the buffer + fills — deadlocking a request/response loop. We launch it under ``stdbuf -oL`` + (configurable) to force line buffering. See ``STDBUF_PREFIX``. +* **stderr is drained separately.** Sparrowhawk logs diagnostics to stderr; we + read those on a background thread so they can never fill the pipe and block the + child, and so stdout carries *only* the normalized text. +""" + +import logging +import os +import queue +import shlex +import subprocess +import threading +from typing import List, Optional + +log = logging.getLogger("sparrowhawk") + + +class NormalizerError(RuntimeError): + pass + + +class NormalizerWorker: + """A single long-running ``normalizer_main`` process for one direction.""" + + def __init__(self, cmd: List[str], cwd: str, io_timeout: float = 15.0): + self.cmd = cmd + self.cwd = cwd + self.io_timeout = io_timeout + self._lock = threading.Lock() + self.proc: Optional[subprocess.Popen] = None + self._out_q: "queue.Queue[Optional[str]]" = queue.Queue() + self._start() + + # -- process lifecycle ------------------------------------------------- + def _start(self) -> None: + log.info("starting: %s (cwd=%s)", " ".join(self.cmd), self.cwd) + # A fresh queue per process so a crash/restart can never leave a stale + # line behind that would desync request N with response N-1. + self._out_q = queue.Queue() + self.proc = subprocess.Popen( + self.cmd, + cwd=self.cwd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + bufsize=1, # line-buffered on our (write) side + ) + threading.Thread(target=self._pump_stdout, args=(self.proc,), daemon=True).start() + threading.Thread(target=self._drain_stderr, args=(self.proc,), daemon=True).start() + + def _pump_stdout(self, proc: subprocess.Popen) -> None: + try: + for line in proc.stdout: # type: ignore[union-attr] + self._out_q.put(line) + finally: + self._out_q.put(None) # sentinel: process closed stdout / died + + def _drain_stderr(self, proc: subprocess.Popen) -> None: + for line in proc.stderr: # type: ignore[union-attr] + log.debug("[normalizer stderr] %s", line.rstrip()) + + def alive(self) -> bool: + return self.proc is not None and self.proc.poll() is None + + def close(self) -> None: + if self.proc and self.proc.poll() is None: + try: + self.proc.stdin.close() # type: ignore[union-attr] + except Exception: + pass + self.proc.terminate() + + # -- request/response -------------------------------------------------- + def normalize(self, text: str) -> str: + # One request is one line, so collapse any embedded newlines. + text = text.replace("\r", " ").replace("\n", " ").strip() + if not text: + return "" + with self._lock: + try: + return self._exchange(text) + except (BrokenPipeError, ValueError, NormalizerError) as e: + # Restart once and retry — covers a crashed/closed child. + log.warning("normalizer exchange failed (%s); restarting worker", e) + self._start() + return self._exchange(text) + + def _exchange(self, text: str) -> str: + if not self.alive(): + self._start() + assert self.proc is not None and self.proc.stdin is not None + self.proc.stdin.write(text + "\n") + self.proc.stdin.flush() + try: + line = self._out_q.get(timeout=self.io_timeout) + except queue.Empty: + self._start() # reset to a known-good state + raise NormalizerError(f"timed out after {self.io_timeout}s waiting for output") + if line is None: + raise NormalizerError("normalizer_main closed its output (process died)") + return line.strip() + + +class EnginePool: + """A fixed pool of workers for one direction, enabling real concurrency. + + Each ``normalizer_main`` process is single-threaded and serializes requests, + so throughput scales with the number of workers. ``normalize`` checks a + worker out of a blocking queue (natural backpressure) and returns it after. + """ + + def __init__(self, name: str, cmd: List[str], cwd: str, size: int = 2, io_timeout: float = 15.0): + self.name = name + self._q: "queue.Queue[NormalizerWorker]" = queue.Queue() + self.workers = [NormalizerWorker(cmd, cwd, io_timeout=io_timeout) for _ in range(max(1, size))] + for w in self.workers: + self._q.put(w) + + def normalize(self, text: str) -> str: + w = self._q.get() + try: + return w.normalize(text) + finally: + self._q.put(w) + + def healthy(self) -> bool: + return all(w.alive() for w in self.workers) + + def close(self) -> None: + for w in self.workers: + w.close() + + +def build_cmd(binary: str, config_name: str, stdbuf_prefix: str) -> List[str]: + """Assemble the argv for launching ``normalizer_main`` under stdbuf.""" + prefix = shlex.split(stdbuf_prefix) if stdbuf_prefix.strip() else [] + return prefix + [binary, f"--config={config_name}"] + + +def pool_from_env(name: str, cwd: str) -> EnginePool: + """Create an :class:`EnginePool` for ``name`` ('tn'/'itn') rooted at ``cwd``.""" + binary = os.environ.get("SPARROWHAWK_BIN", "normalizer_main") + config_name = os.environ.get("SPARROWHAWK_CONFIG", "sparrowhawk_configuration.ascii_proto") + stdbuf_prefix = os.environ.get("STDBUF_PREFIX", "stdbuf -oL -eL") + workers = int(os.environ.get("WORKERS_PER_DIRECTION", "2")) + io_timeout = float(os.environ.get("IO_TIMEOUT", "15")) + cmd = build_cmd(binary, config_name, stdbuf_prefix) + return EnginePool(name, cmd, cwd, size=workers, io_timeout=io_timeout) diff --git a/tools/text_processing_deployment/server/requirements.txt b/tools/text_processing_deployment/server/requirements.txt new file mode 100644 index 000000000..d9e93a2ec --- /dev/null +++ b/tools/text_processing_deployment/server/requirements.txt @@ -0,0 +1,3 @@ +fastapi==0.115.6 +pydantic==2.10.4 +uvicorn[standard]==0.34.0 diff --git a/tools/text_processing_deployment/server/tests/fake_normalizer_main.py b/tools/text_processing_deployment/server/tests/fake_normalizer_main.py new file mode 100755 index 000000000..fdb474c7f --- /dev/null +++ b/tools/text_processing_deployment/server/tests/fake_normalizer_main.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Stand-in for Sparrowhawk's `normalizer_main` used only for local testing of +# the server wrapper (the real binary needs the Linux x86_64 Sparrowhawk image). +# +# It mimics the real contract: reads one utterance per line from stdin, writes +# exactly one normalized line to stdout, and emits diagnostic noise on stderr — +# so it exercises the wrapper's "stdout = result, stderr = drained" split. +# +# Direction is read from a `MODE` file in the current working directory +# (`upper` or `lower`), mirroring how the real setup selects grammars by cwd. +import os +import sys + +mode = "upper" +try: + with open(os.path.join(os.getcwd(), "MODE")) as fh: + mode = fh.read().strip() or "upper" +except FileNotFoundError: + pass + +# Simulate the one-time grammar load, logged to stderr like the real binary. +print(f"[fake normalizer] loaded grammars (mode={mode})", file=sys.stderr, flush=True) + +for line in sys.stdin: + text = line.rstrip("\n") + print(f"[fake normalizer] normalizing: {text!r}", file=sys.stderr, flush=True) + result = text.upper() if mode == "upper" else text.lower() + sys.stdout.write(result + "\n") + sys.stdout.flush() diff --git a/tools/text_processing_deployment/server/tests/test_local.sh b/tools/text_processing_deployment/server/tests/test_local.sh new file mode 100755 index 000000000..0f384b256 --- /dev/null +++ b/tools/text_processing_deployment/server/tests/test_local.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# End-to-end smoke test of the HTTP server against the fake normalizer_main. +# Validates the pipe wrapper, engine pool, and all endpoints without needing the +# real Sparrowhawk image. Requires: fastapi, uvicorn, httpx (or curl). +# +# PYTHON=/path/to/python bash server/tests/test_local.sh +set -euo pipefail + +PYTHON=${PYTHON:-python3} +SCRIPT_DIR=$(cd "$(dirname "$0")"; pwd) +SERVER_DIR=$(cd "$SCRIPT_DIR/.."; pwd) +WORK=$(mktemp -d) +trap 'kill "${SERVER_PID:-}" 2>/dev/null || true; rm -rf "$WORK"' EXIT + +# Two serving dirs, each with a config marker (so the server enables the +# direction) and a MODE file the fake reads to pick its transform. +mkdir -p "$WORK/tn" "$WORK/itn" +: > "$WORK/tn/sparrowhawk_configuration.ascii_proto" +: > "$WORK/itn/sparrowhawk_configuration.ascii_proto" +echo upper > "$WORK/tn/MODE" +echo lower > "$WORK/itn/MODE" + +chmod +x "$SCRIPT_DIR/fake_normalizer_main.py" + +PORT=${PORT:-8137} +export SPARROWHAWK_BIN="$SCRIPT_DIR/fake_normalizer_main.py" +export STDBUF_PREFIX="" # macOS has no stdbuf; the fake flushes itself +export TN_DIR="$WORK/tn" +export ITN_DIR="$WORK/itn" +export WORKERS_PER_DIRECTION=2 +export IO_TIMEOUT=5 +export PORT + +echo "[test] starting server on :$PORT" +( cd "$SERVER_DIR" && "$PYTHON" -m uvicorn app:app --host 127.0.0.1 --port "$PORT" --log-level warning ) & +SERVER_PID=$! + +# Wait for readiness. +for _ in $(seq 1 50); do + if curl -fsS "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then break; fi + sleep 0.2 +done + +fail=0 +check () { + local label=$1 got=$2 want=$3 + if [[ "$got" == "$want" ]]; then + echo " PASS $label -> $got" + else + echo " FAIL $label -> got [$got] want [$want]"; fail=1 + fi +} + +echo "[test] /health"; curl -fsS "http://127.0.0.1:$PORT/health"; echo + +TN=$(curl -fsS -X POST "http://127.0.0.1:$PORT/tn" -H 'content-type: application/json' \ + -d '{"text":"δεκα μετρα"}' | "$PYTHON" -c 'import sys,json;print(json.load(sys.stdin)["results"][0])') +check "tn single" "$TN" "ΔΕΚΑ ΜΕΤΡΑ" + +ITN=$(curl -fsS -X POST "http://127.0.0.1:$PORT/itn" -H 'content-type: application/json' \ + -d '{"text":"ΔΕΚΑΤΕΣΣΕΡΙΣ"}' | "$PYTHON" -c 'import sys,json;print(json.load(sys.stdin)["results"][0])') +check "itn single" "$ITN" "δεκατεσσερις" + +BATCH=$(curl -fsS -X POST "http://127.0.0.1:$PORT/normalize" -H 'content-type: application/json' \ + -d '{"direction":"tn","texts":["ena","dyo tria"]}' | "$PYTHON" -c 'import sys,json;print("|".join(json.load(sys.stdin)["results"]))') +check "tn batch" "$BATCH" "ENA|DYO TRIA" + +# many requests to exercise the pool + checkout/return under load +OK=1 +for i in $(seq 1 30); do + R=$(curl -fsS -X POST "http://127.0.0.1:$PORT/tn" -H 'content-type: application/json' -d "{\"text\":\"x$i\"}" \ + | "$PYTHON" -c 'import sys,json;print(json.load(sys.stdin)["results"][0])') + [[ "$R" == "X$i" ]] || OK=0 +done +check "pool load (30 reqs)" "$OK" "1" + +if [[ $fail -eq 0 ]]; then echo "[test] ALL PASSED"; else echo "[test] FAILURES"; exit 1; fi