From f0ef2b5db27b8f6e8abcce2aef261fbcb8575793 Mon Sep 17 00:00:00 2001 From: kba Date: Wed, 1 Oct 2025 18:10:13 +0200 Subject: [PATCH 1/6] remove unused imports --- train/gt_gen_utils.py | 4 +--- train/inference.py | 6 ------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/train/gt_gen_utils.py b/train/gt_gen_utils.py index 38d48ca..2828d7b 100644 --- a/train/gt_gen_utils.py +++ b/train/gt_gen_utils.py @@ -1,5 +1,3 @@ -import click -import sys import os import numpy as np import warnings @@ -9,7 +7,7 @@ import cv2 from shapely import geometry from pathlib import Path import matplotlib.pyplot as plt -from PIL import Image, ImageDraw, ImageFont +from PIL import ImageFont KERNEL = np.ones((5, 5), np.uint8) diff --git a/train/inference.py b/train/inference.py index 595cfe7..0bff0ec 100644 --- a/train/inference.py +++ b/train/inference.py @@ -3,12 +3,9 @@ import os import numpy as np import warnings import cv2 -import seaborn as sns from tensorflow.keras.models import load_model import tensorflow as tf from tensorflow.keras import backend as K -from tensorflow.keras import layers -import tensorflow.keras.losses from tensorflow.keras.layers import * from models import * from gt_gen_utils import * @@ -16,7 +13,6 @@ import click import json from tensorflow.python.keras import backend as tensorflow_backend import xml.etree.ElementTree as ET -import matplotlib.pyplot as plt with warnings.catch_warnings(): @@ -55,11 +51,9 @@ class sbb_predict: seg=seg[:,:,0] seg_img=np.zeros((np.shape(seg)[0],np.shape(seg)[1],3)).astype(np.uint8) - colors=sns.color_palette("hls", self.n_classes) for c in ann_u: c=int(c) - segl=(seg==c) seg_img[:,:,0][seg==c]=c seg_img[:,:,1][seg==c]=c seg_img[:,:,2][seg==c]=c From 4f5cdf314004b6bb0a409aee7b3525391f8afcc7 Mon Sep 17 00:00:00 2001 From: kba Date: Wed, 1 Oct 2025 18:12:45 +0200 Subject: [PATCH 2/6] move training scripts to src/eynollah/training --- pyproject.toml | 1 + {train => src/eynollah/training}/__init__.py | 0 .../training}/build_model_load_pretrained_weights_and_save.py | 0 {train => src/eynollah/training}/generate_gt_for_training.py | 0 {train => src/eynollah/training}/gt_gen_utils.py | 0 {train => src/eynollah/training}/inference.py | 0 {train => src/eynollah/training}/metrics.py | 0 {train => src/eynollah/training}/models.py | 0 {train => src/eynollah/training}/train.py | 0 {train => src/eynollah/training}/utils.py | 0 10 files changed, 1 insertion(+) rename {train => src/eynollah/training}/__init__.py (100%) rename {train => src/eynollah/training}/build_model_load_pretrained_weights_and_save.py (100%) rename {train => src/eynollah/training}/generate_gt_for_training.py (100%) rename {train => src/eynollah/training}/gt_gen_utils.py (100%) rename {train => src/eynollah/training}/inference.py (100%) rename {train => src/eynollah/training}/metrics.py (100%) rename {train => src/eynollah/training}/models.py (100%) rename {train => src/eynollah/training}/train.py (100%) rename {train => src/eynollah/training}/utils.py (100%) diff --git a/pyproject.toml b/pyproject.toml index ec3e5f8..8ca6cff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ classifiers = [ eynollah = "eynollah.cli:main" ocrd-eynollah-segment = "eynollah.ocrd_cli:main" ocrd-sbb-binarize = "eynollah.ocrd_cli_binarization:main" +eynollah-training = "eynollah.training.cli:main" [project.urls] Homepage = "https://github.com/qurator-spk/eynollah" diff --git a/train/__init__.py b/src/eynollah/training/__init__.py similarity index 100% rename from train/__init__.py rename to src/eynollah/training/__init__.py diff --git a/train/build_model_load_pretrained_weights_and_save.py b/src/eynollah/training/build_model_load_pretrained_weights_and_save.py similarity index 100% rename from train/build_model_load_pretrained_weights_and_save.py rename to src/eynollah/training/build_model_load_pretrained_weights_and_save.py diff --git a/train/generate_gt_for_training.py b/src/eynollah/training/generate_gt_for_training.py similarity index 100% rename from train/generate_gt_for_training.py rename to src/eynollah/training/generate_gt_for_training.py diff --git a/train/gt_gen_utils.py b/src/eynollah/training/gt_gen_utils.py similarity index 100% rename from train/gt_gen_utils.py rename to src/eynollah/training/gt_gen_utils.py diff --git a/train/inference.py b/src/eynollah/training/inference.py similarity index 100% rename from train/inference.py rename to src/eynollah/training/inference.py diff --git a/train/metrics.py b/src/eynollah/training/metrics.py similarity index 100% rename from train/metrics.py rename to src/eynollah/training/metrics.py diff --git a/train/models.py b/src/eynollah/training/models.py similarity index 100% rename from train/models.py rename to src/eynollah/training/models.py diff --git a/train/train.py b/src/eynollah/training/train.py similarity index 100% rename from train/train.py rename to src/eynollah/training/train.py diff --git a/train/utils.py b/src/eynollah/training/utils.py similarity index 100% rename from train/utils.py rename to src/eynollah/training/utils.py From 2baf42e878732330c0df54927c55a1ef9a9c8b03 Mon Sep 17 00:00:00 2001 From: kba Date: Wed, 1 Oct 2025 18:15:54 +0200 Subject: [PATCH 3/6] organize imports, use relative imports --- src/eynollah/training/generate_gt_for_training.py | 3 ++- src/eynollah/training/gt_gen_utils.py | 1 - src/eynollah/training/inference.py | 10 ++++++---- src/eynollah/training/train.py | 10 ++++++---- src/eynollah/training/utils.py | 5 +++-- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/eynollah/training/generate_gt_for_training.py b/src/eynollah/training/generate_gt_for_training.py index 388fced..d378c3e 100644 --- a/src/eynollah/training/generate_gt_for_training.py +++ b/src/eynollah/training/generate_gt_for_training.py @@ -1,10 +1,11 @@ import click import json -from gt_gen_utils import * from tqdm import tqdm from pathlib import Path from PIL import Image, ImageDraw, ImageFont +from .gt_gen_utils import * + @click.group() def main(): pass diff --git a/src/eynollah/training/gt_gen_utils.py b/src/eynollah/training/gt_gen_utils.py index 2828d7b..2e3428b 100644 --- a/src/eynollah/training/gt_gen_utils.py +++ b/src/eynollah/training/gt_gen_utils.py @@ -6,7 +6,6 @@ from tqdm import tqdm import cv2 from shapely import geometry from pathlib import Path -import matplotlib.pyplot as plt from PIL import ImageFont diff --git a/src/eynollah/training/inference.py b/src/eynollah/training/inference.py index 0bff0ec..24837a1 100644 --- a/src/eynollah/training/inference.py +++ b/src/eynollah/training/inference.py @@ -1,19 +1,21 @@ import sys import os -import numpy as np import warnings +import json + +import numpy as np import cv2 from tensorflow.keras.models import load_model import tensorflow as tf from tensorflow.keras import backend as K from tensorflow.keras.layers import * -from models import * -from gt_gen_utils import * import click -import json from tensorflow.python.keras import backend as tensorflow_backend import xml.etree.ElementTree as ET +from .models import * +from .gt_gen_utils import * + with warnings.catch_warnings(): warnings.simplefilter("ignore") diff --git a/src/eynollah/training/train.py b/src/eynollah/training/train.py index add878a..3b99807 100644 --- a/src/eynollah/training/train.py +++ b/src/eynollah/training/train.py @@ -1,20 +1,22 @@ import os import sys +import json + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' import tensorflow as tf from tensorflow.compat.v1.keras.backend import set_session import warnings from tensorflow.keras.optimizers import * from sacred import Experiment -from models import * -from utils import * -from metrics import * from tensorflow.keras.models import load_model from tqdm import tqdm -import json from sklearn.metrics import f1_score from tensorflow.keras.callbacks import Callback +from .models import * +from .utils import * +from .metrics import * + class SaveWeightsAfterSteps(Callback): def __init__(self, save_interval, save_path, _config): super(SaveWeightsAfterSteps, self).__init__() diff --git a/src/eynollah/training/utils.py b/src/eynollah/training/utils.py index ead4887..1278be5 100644 --- a/src/eynollah/training/utils.py +++ b/src/eynollah/training/utils.py @@ -1,13 +1,14 @@ import os +import math +import random + import cv2 import numpy as np import seaborn as sns from scipy.ndimage.interpolation import map_coordinates from scipy.ndimage.filters import gaussian_filter -import random from tqdm import tqdm import imutils -import math from tensorflow.keras.utils import to_categorical from PIL import Image, ImageEnhance From 690d47444caab7a8f2ba5443c0cb1701383c46e3 Mon Sep 17 00:00:00 2001 From: kba Date: Wed, 1 Oct 2025 18:36:28 +0200 Subject: [PATCH 4/6] make relative wildcard imports explicit --- pyproject.toml | 1 - ..._model_load_pretrained_weights_and_save.py | 9 ++--- .../training/generate_gt_for_training.py | 20 ++++++++++- src/eynollah/training/inference.py | 11 +++++-- src/eynollah/training/train.py | 33 +++++++++++++++---- 5 files changed, 55 insertions(+), 19 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8ca6cff..ec3e5f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,6 @@ classifiers = [ eynollah = "eynollah.cli:main" ocrd-eynollah-segment = "eynollah.ocrd_cli:main" ocrd-sbb-binarize = "eynollah.ocrd_cli_binarization:main" -eynollah-training = "eynollah.training.cli:main" [project.urls] Homepage = "https://github.com/qurator-spk/eynollah" diff --git a/src/eynollah/training/build_model_load_pretrained_weights_and_save.py b/src/eynollah/training/build_model_load_pretrained_weights_and_save.py index 125611e..ce3d955 100644 --- a/src/eynollah/training/build_model_load_pretrained_weights_and_save.py +++ b/src/eynollah/training/build_model_load_pretrained_weights_and_save.py @@ -1,12 +1,7 @@ -import os -import sys import tensorflow as tf -import warnings from tensorflow.keras.optimizers import * -from sacred import Experiment -from models import * -from utils import * -from metrics import * + +from .models import resnet50_unet def configuration(): diff --git a/src/eynollah/training/generate_gt_for_training.py b/src/eynollah/training/generate_gt_for_training.py index d378c3e..3fd93ae 100644 --- a/src/eynollah/training/generate_gt_for_training.py +++ b/src/eynollah/training/generate_gt_for_training.py @@ -1,10 +1,28 @@ import click import json +import os from tqdm import tqdm from pathlib import Path from PIL import Image, ImageDraw, ImageFont +import cv2 +import numpy as np -from .gt_gen_utils import * +from eynollah.training.gt_gen_utils import ( + filter_contours_area_of_image, + find_format_of_given_filename_in_dir, + find_new_features_of_contours, + fit_text_single_line, + get_content_of_dir, + get_images_of_ground_truth, + get_layout_contours_for_visualization, + get_textline_contours_and_ocr_text, + get_textline_contours_for_visualization, + overlay_layout_on_image, + read_xml, + resize_image, + visualize_image_from_contours, + visualize_image_from_contours_layout +) @click.group() def main(): diff --git a/src/eynollah/training/inference.py b/src/eynollah/training/inference.py index 24837a1..998c8fc 100644 --- a/src/eynollah/training/inference.py +++ b/src/eynollah/training/inference.py @@ -13,9 +13,14 @@ import click from tensorflow.python.keras import backend as tensorflow_backend import xml.etree.ElementTree as ET -from .models import * -from .gt_gen_utils import * - +from .gt_gen_utils import ( + filter_contours_area_of_image, + find_new_features_of_contours, + read_xml, + resize_image, + update_list_and_return_first_with_length_bigger_than_one +) +from .models import PatchEncoder, Patches with warnings.catch_warnings(): warnings.simplefilter("ignore") diff --git a/src/eynollah/training/train.py b/src/eynollah/training/train.py index 3b99807..527bca6 100644 --- a/src/eynollah/training/train.py +++ b/src/eynollah/training/train.py @@ -2,20 +2,39 @@ import os import sys import json +from eynollah.training.metrics import soft_dice_loss, weighted_categorical_crossentropy + +from .models import ( + PatchEncoder, + Patches, + machine_based_reading_order_model, + resnet50_classifier, + resnet50_unet, + vit_resnet50_unet, + vit_resnet50_unet_transformer_before_cnn +) +from .utils import ( + data_gen, + generate_arrays_from_folder_reading_order, + generate_data_from_folder_evaluation, + generate_data_from_folder_training, + get_one_hot, + provide_patches, + return_number_of_total_training_data +) + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' import tensorflow as tf from tensorflow.compat.v1.keras.backend import set_session -import warnings -from tensorflow.keras.optimizers import * +from tensorflow.keras.optimizers import SGD, Adam from sacred import Experiment from tensorflow.keras.models import load_model from tqdm import tqdm from sklearn.metrics import f1_score from tensorflow.keras.callbacks import Callback -from .models import * -from .utils import * -from .metrics import * +import numpy as np +import cv2 class SaveWeightsAfterSteps(Callback): def __init__(self, save_interval, save_path, _config): @@ -47,8 +66,8 @@ def configuration(): def get_dirs_or_files(input_data): + image_input, labels_input = os.path.join(input_data, 'images/'), os.path.join(input_data, 'labels/') if os.path.isdir(input_data): - image_input, labels_input = os.path.join(input_data, 'images/'), os.path.join(input_data, 'labels/') # Check if training dir exists assert os.path.isdir(image_input), "{} is not a directory".format(image_input) assert os.path.isdir(labels_input), "{} is not a directory".format(labels_input) @@ -425,7 +444,7 @@ def run(_config, n_classes, n_epochs, input_height, #f1score_tot = [0] indexer_start = 0 - opt = SGD(learning_rate=0.01, momentum=0.9) + # opt = SGD(learning_rate=0.01, momentum=0.9) opt_adam = tf.keras.optimizers.Adam(learning_rate=0.0001) model.compile(loss="binary_crossentropy", optimizer = opt_adam,metrics=['accuracy']) From 1c043c586a972c4088d204b179b37d64eb44a39f Mon Sep 17 00:00:00 2001 From: kba Date: Wed, 1 Oct 2025 18:52:11 +0200 Subject: [PATCH 5/6] eynollah-training: all training CLI into single click group --- pyproject.toml | 1 + ..._model_load_pretrained_weights_and_save.py | 6 ++--- src/eynollah/training/cli.py | 26 +++++++++++++++++++ .../training/generate_gt_for_training.py | 3 --- src/eynollah/training/inference.py | 11 +++----- src/eynollah/training/train.py | 11 +++++--- 6 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 src/eynollah/training/cli.py diff --git a/pyproject.toml b/pyproject.toml index ec3e5f8..ec99c99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ classifiers = [ [project.scripts] eynollah = "eynollah.cli:main" +eynollah-training = "eynollah.training.cli:main" ocrd-eynollah-segment = "eynollah.ocrd_cli:main" ocrd-sbb-binarize = "eynollah.ocrd_cli_binarization:main" diff --git a/src/eynollah/training/build_model_load_pretrained_weights_and_save.py b/src/eynollah/training/build_model_load_pretrained_weights_and_save.py index ce3d955..40fc1fe 100644 --- a/src/eynollah/training/build_model_load_pretrained_weights_and_save.py +++ b/src/eynollah/training/build_model_load_pretrained_weights_and_save.py @@ -1,5 +1,5 @@ +import click import tensorflow as tf -from tensorflow.keras.optimizers import * from .models import resnet50_unet @@ -8,8 +8,8 @@ def configuration(): gpu_options = tf.compat.v1.GPUOptions(allow_growth=True) session = tf.compat.v1.Session(config=tf.compat.v1.ConfigProto(gpu_options=gpu_options)) - -if __name__ == '__main__': +@click.command() +def build_model_load_pretrained_weights_and_save(): n_classes = 2 input_height = 224 input_width = 448 diff --git a/src/eynollah/training/cli.py b/src/eynollah/training/cli.py new file mode 100644 index 0000000..8ab754d --- /dev/null +++ b/src/eynollah/training/cli.py @@ -0,0 +1,26 @@ +import os +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' + +import click +import sys + +from .build_model_load_pretrained_weights_and_save import build_model_load_pretrained_weights_and_save +from .generate_gt_for_training import main as generate_gt_cli +from .inference import main as inference_cli +from .train import ex + +@click.command(context_settings=dict( + ignore_unknown_options=True, +)) +@click.argument('SACRED_ARGS', nargs=-1, type=click.UNPROCESSED) +def train_cli(sacred_args): + ex.run_commandline([sys.argv[0]] + list(sacred_args)) + +@click.group('training') +def main(): + pass + +main.add_command(build_model_load_pretrained_weights_and_save) +main.add_command(generate_gt_cli, 'generate-gt') +main.add_command(inference_cli, 'inference') +main.add_command(train_cli, 'train') diff --git a/src/eynollah/training/generate_gt_for_training.py b/src/eynollah/training/generate_gt_for_training.py index 3fd93ae..693cab8 100644 --- a/src/eynollah/training/generate_gt_for_training.py +++ b/src/eynollah/training/generate_gt_for_training.py @@ -581,6 +581,3 @@ def visualize_ocr_text(xml_file, dir_xml, dir_out): # Draw the text draw.text((text_x, text_y), ocr_texts[index], fill="black", font=font) image_text.save(os.path.join(dir_out, f_name+'.png')) - -if __name__ == "__main__": - main() diff --git a/src/eynollah/training/inference.py b/src/eynollah/training/inference.py index 998c8fc..3fa8fd6 100644 --- a/src/eynollah/training/inference.py +++ b/src/eynollah/training/inference.py @@ -20,7 +20,10 @@ from .gt_gen_utils import ( resize_image, update_list_and_return_first_with_length_bigger_than_one ) -from .models import PatchEncoder, Patches +from .models import ( + PatchEncoder, + Patches +) with warnings.catch_warnings(): warnings.simplefilter("ignore") @@ -675,9 +678,3 @@ def main(image, dir_in, model, patches, save, save_layout, ground_truth, xml_fil x=sbb_predict(image, dir_in, model, task, config_params_model, patches, save, save_layout, ground_truth, xml_file, out, min_area) x.run() -if __name__=="__main__": - main() - - - - diff --git a/src/eynollah/training/train.py b/src/eynollah/training/train.py index 527bca6..97736e0 100644 --- a/src/eynollah/training/train.py +++ b/src/eynollah/training/train.py @@ -2,9 +2,13 @@ import os import sys import json -from eynollah.training.metrics import soft_dice_loss, weighted_categorical_crossentropy +import click -from .models import ( +from eynollah.training.metrics import ( + soft_dice_loss, + weighted_categorical_crossentropy +) +from eynollah.training.models import ( PatchEncoder, Patches, machine_based_reading_order_model, @@ -13,7 +17,7 @@ from .models import ( vit_resnet50_unet, vit_resnet50_unet_transformer_before_cnn ) -from .utils import ( +from eynollah.training.utils import ( data_gen, generate_arrays_from_folder_reading_order, generate_data_from_folder_evaluation, @@ -142,7 +146,6 @@ def config_params(): dir_rgb_backgrounds = None dir_rgb_foregrounds = None - @ex.automain def run(_config, n_classes, n_epochs, input_height, input_width, weight_decay, weighted_loss, From f60e0543ab293212c2d0e5791c0efa8658cc0ac4 Mon Sep 17 00:00:00 2001 From: kba Date: Wed, 1 Oct 2025 19:16:58 +0200 Subject: [PATCH 6/6] training: update docs --- docs/train.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/docs/train.md b/docs/train.md index 839529f..252bead 100644 --- a/docs/train.md +++ b/docs/train.md @@ -13,7 +13,7 @@ The following three tasks can all be accomplished using the code in the * train a model * inference with the trained model -## Training , evaluation and output +## Training, evaluation and output The train and evaluation folders should contain subfolders of `images` and `labels`. @@ -22,11 +22,13 @@ The output folder should be an empty folder where the output model will be writt ## Generate training dataset The script `generate_gt_for_training.py` is used for generating training datasets. As the results of the following -command demonstrates, the dataset generator provides three different commands: +command demonstrates, the dataset generator provides several subcommands: -`python generate_gt_for_training.py --help` +```sh +eynollah-training generate-gt --help +``` -These three commands are: +The three most important subcommands are: * image-enhancement * machine-based-reading-order @@ -38,7 +40,7 @@ Generating a training dataset for image enhancement is quite straightforward. Al high-resolution images. The training dataset can then be generated using the following command: ```sh -python generate_gt_for_training.py image-enhancement \ +eynollah-training image-enhancement \ -dis "dir of high resolution images" \ -dois "dir where degraded images will be written" \ -dols "dir where the corresponding high resolution image will be written as label" \ @@ -69,7 +71,7 @@ to filter out regions smaller than this minimum size. This minimum size is defin to the image area, with a default value of zero. To run the dataset generator, use the following command: ```shell -python generate_gt_for_training.py machine-based-reading-order \ +eynollah-training generate-gt machine-based-reading-order \ -dx "dir of GT xml files" \ -domi "dir where output images will be written" \ "" -docl "dir where the labels will be written" \ @@ -144,7 +146,7 @@ region" are also present in the label. However, other regions like "noise region included in the label PNG file, even if they have information in the page XML files, as we chose not to include them. ```sh -python generate_gt_for_training.py pagexml2label \ +eynollah-training generate-gt pagexml2label \ -dx "dir of GT xml files" \ -do "dir where output label png files will be written" \ -cfg "custom config json file" \ @@ -198,7 +200,7 @@ provided to ensure that they are cropped in sync with the labels. This ensures t required for training are obtained. The command should resemble the following: ```sh -python generate_gt_for_training.py pagexml2label \ +eynollah-training generate-gt pagexml2label \ -dx "dir of GT xml files" \ -do "dir where output label png files will be written" \ -cfg "custom config json file" \ @@ -261,7 +263,7 @@ And the "dir_eval" the same structure as train directory: The classification model can be trained using the following command line: ```sh -python train.py with config_classification.json +eynollah-training train with config_classification.json ``` As evident in the example JSON file above, for classification, we utilize a "f1_threshold_classification" parameter. @@ -395,7 +397,9 @@ And the "dir_eval" the same structure as train directory: After configuring the JSON file for segmentation or enhancement, training can be initiated by running the following command, similar to the process for classification and reading order: -`python train.py with config_classification.json` +``` +eynollah-training train with config_classification.json` +``` #### Binarization @@ -679,7 +683,7 @@ For conducting inference with a trained model, you simply need to execute the fo directory of the model and the image on which to perform inference: ```sh -python inference.py -m "model dir" -i "image" +eynollah-training inference -m "model dir" -i "image" ``` This will straightforwardly return the class of the image. @@ -691,7 +695,7 @@ without the reading order. We simply need to provide the model directory, the XM new XML file with the added reading order will be written to the output directory with the same name. We need to run: ```sh -python inference.py \ +eynollah-training inference \ -m "model dir" \ -xml "page xml file" \ -o "output dir to write new xml with reading order" @@ -702,7 +706,7 @@ python inference.py \ For conducting inference with a trained model for segmentation and enhancement you need to run the following command line: ```sh -python inference.py \ +eynollah-training inference \ -m "model dir" \ -i "image" \ -p \