matrice-analytics 0.1.60__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matrice_analytics/__init__.py +28 -0
- matrice_analytics/boundary_drawing_internal/README.md +305 -0
- matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
- matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
- matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
- matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
- matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
- matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
- matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
- matrice_analytics/post_processing/README.md +455 -0
- matrice_analytics/post_processing/__init__.py +732 -0
- matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
- matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
- matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
- matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
- matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
- matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
- matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
- matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
- matrice_analytics/post_processing/config.py +146 -0
- matrice_analytics/post_processing/core/__init__.py +63 -0
- matrice_analytics/post_processing/core/base.py +704 -0
- matrice_analytics/post_processing/core/config.py +3291 -0
- matrice_analytics/post_processing/core/config_utils.py +925 -0
- matrice_analytics/post_processing/face_reg/__init__.py +43 -0
- matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
- matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
- matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
- matrice_analytics/post_processing/ocr/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
- matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
- matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
- matrice_analytics/post_processing/post_processor.py +1175 -0
- matrice_analytics/post_processing/test_cases/__init__.py +1 -0
- matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
- matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
- matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
- matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
- matrice_analytics/post_processing/test_cases/test_config.py +852 -0
- matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
- matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
- matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
- matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
- matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
- matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
- matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
- matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
- matrice_analytics/post_processing/usecases/__init__.py +267 -0
- matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
- matrice_analytics/post_processing/usecases/age_detection.py +842 -0
- matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
- matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
- matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
- matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
- matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
- matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
- matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
- matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
- matrice_analytics/post_processing/usecases/car_service.py +1601 -0
- matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
- matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
- matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
- matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
- matrice_analytics/post_processing/usecases/color/clip.py +660 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
- matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
- matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
- matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
- matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
- matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
- matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
- matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
- matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
- matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
- matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
- matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
- matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
- matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
- matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
- matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
- matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
- matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
- matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
- matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
- matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
- matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
- matrice_analytics/post_processing/usecases/leaf.py +821 -0
- matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
- matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
- matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
- matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
- matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
- matrice_analytics/post_processing/usecases/parking.py +787 -0
- matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
- matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
- matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
- matrice_analytics/post_processing/usecases/people_counting.py +706 -0
- matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
- matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
- matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
- matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
- matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
- matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
- matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
- matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
- matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
- matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
- matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
- matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
- matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
- matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
- matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
- matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
- matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
- matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
- matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
- matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
- matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
- matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
- matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
- matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
- matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
- matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
- matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
- matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
- matrice_analytics/post_processing/utils/__init__.py +150 -0
- matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
- matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
- matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
- matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
- matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
- matrice_analytics/post_processing/utils/color_utils.py +592 -0
- matrice_analytics/post_processing/utils/counting_utils.py +182 -0
- matrice_analytics/post_processing/utils/filter_utils.py +261 -0
- matrice_analytics/post_processing/utils/format_utils.py +293 -0
- matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
- matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
- matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
- matrice_analytics/py.typed +0 -0
- matrice_analytics-0.1.60.dist-info/METADATA +481 -0
- matrice_analytics-0.1.60.dist-info/RECORD +196 -0
- matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
- matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
- matrice_analytics-0.1.60.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validate a `fast-plate-ocr` dataset before training.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from collections import Counter
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
import click
|
|
12
|
+
import pandas as pd
|
|
13
|
+
from PIL import Image, UnidentifiedImageError
|
|
14
|
+
from rich import box
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.markup import escape
|
|
17
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskID, TextColumn
|
|
18
|
+
from rich.table import Table
|
|
19
|
+
|
|
20
|
+
from fast_plate_ocr.train.model.config import load_plate_config_from_yaml
|
|
21
|
+
|
|
22
|
+
# pylint: disable=too-many-locals
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def partial_decode_ok(path: Path) -> tuple[bool, Optional[tuple[int, int]]]:
|
|
28
|
+
try:
|
|
29
|
+
with Image.open(path) as im:
|
|
30
|
+
im.verify()
|
|
31
|
+
w, h = im.size
|
|
32
|
+
return True, (h, w)
|
|
33
|
+
except (UnidentifiedImageError, OSError):
|
|
34
|
+
return False, None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _validate_dataset(
|
|
38
|
+
df: pd.DataFrame,
|
|
39
|
+
cfg,
|
|
40
|
+
min_h: int,
|
|
41
|
+
min_w: int,
|
|
42
|
+
) -> tuple[list[tuple[str, str]], list[tuple[str, str]], pd.DataFrame]:
|
|
43
|
+
"""
|
|
44
|
+
Iterate over the dataframe, collect errors and warnings, and return a cleaned df.
|
|
45
|
+
"""
|
|
46
|
+
errors, warnings, ok_rows = [], [], []
|
|
47
|
+
char_counter: Counter[str] = Counter()
|
|
48
|
+
seen_paths: set[Path] = set()
|
|
49
|
+
|
|
50
|
+
progress = Progress(
|
|
51
|
+
SpinnerColumn(),
|
|
52
|
+
BarColumn(bar_width=None),
|
|
53
|
+
TextColumn("{task.completed}/{task.total}"),
|
|
54
|
+
TextColumn("[bold blue]{task.description}"),
|
|
55
|
+
transient=True,
|
|
56
|
+
console=console,
|
|
57
|
+
)
|
|
58
|
+
task: TaskID = progress.add_task("Scanning images", total=len(df))
|
|
59
|
+
|
|
60
|
+
with progress:
|
|
61
|
+
for row_idx, row in enumerate(df.itertuples(index=False)):
|
|
62
|
+
img_path = Path(str(row.image_path))
|
|
63
|
+
plate = str(row.plate_text)
|
|
64
|
+
char_counter.update(plate)
|
|
65
|
+
line_no = str(row_idx + 2)
|
|
66
|
+
|
|
67
|
+
# Check file exists
|
|
68
|
+
if not img_path.exists():
|
|
69
|
+
errors.append((line_no, f"Missing image file: {img_path}"))
|
|
70
|
+
progress.update(task, advance=1)
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
# Check decodable & size
|
|
74
|
+
ok, shape = partial_decode_ok(img_path)
|
|
75
|
+
if not ok or shape is None:
|
|
76
|
+
errors.append((line_no, f"Corrupt or unreadable image: {img_path}"))
|
|
77
|
+
progress.update(task, advance=1)
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
h, w = shape
|
|
81
|
+
if h < min_h or w < min_w:
|
|
82
|
+
warnings.append((line_no, f"Tiny image ({h}x{w} < {min_h}x{min_w}): {img_path}"))
|
|
83
|
+
|
|
84
|
+
# Check resize feasibility (>= 1 px in each axis)
|
|
85
|
+
r = min(cfg.img_height / h, cfg.img_width / w)
|
|
86
|
+
new_w, new_h = round(w * r), round(h * r)
|
|
87
|
+
if new_w == 0 or new_h == 0:
|
|
88
|
+
errors.append((line_no, f"Resize would give 0x0 ({new_h}x{new_w}) from {img_path}"))
|
|
89
|
+
progress.update(task, advance=1)
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Check plate text length and alphabet
|
|
93
|
+
if len(plate) > cfg.max_plate_slots:
|
|
94
|
+
errors.append(
|
|
95
|
+
(
|
|
96
|
+
line_no,
|
|
97
|
+
f"Plate too long ({len(plate)}>{cfg.max_plate_slots}):"
|
|
98
|
+
f" '{plate}' [{img_path}]",
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
progress.update(task, advance=1)
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
bad_chars = set(plate) - set(cfg.alphabet)
|
|
105
|
+
if bad_chars:
|
|
106
|
+
errors.append(
|
|
107
|
+
(line_no, f"Invalid chars {bad_chars} in plate '{plate}' [{img_path}]")
|
|
108
|
+
)
|
|
109
|
+
progress.update(task, advance=1)
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
# Check duplicate paths
|
|
113
|
+
if img_path in seen_paths:
|
|
114
|
+
warnings.append((line_no, f"Duplicate image entry: {img_path}"))
|
|
115
|
+
progress.update(task, advance=1)
|
|
116
|
+
continue
|
|
117
|
+
seen_paths.add(img_path)
|
|
118
|
+
|
|
119
|
+
ok_rows.append(row)
|
|
120
|
+
progress.update(task, advance=1)
|
|
121
|
+
|
|
122
|
+
alphabet_no_pad = cfg.alphabet.replace(cfg.pad_char, "")
|
|
123
|
+
unused_chars = sorted(set(alphabet_no_pad) - set(char_counter))
|
|
124
|
+
if unused_chars:
|
|
125
|
+
warnings.append(("-", f"Character(s) not found in any plate: {', '.join(unused_chars)}"))
|
|
126
|
+
cleaned_df = pd.DataFrame(ok_rows)
|
|
127
|
+
return errors, warnings, cleaned_df
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def rich_report(errors, warnings):
|
|
131
|
+
summary = Table(title="Validation Summary", box=box.SQUARE, expand=False)
|
|
132
|
+
summary.add_column("Category", style="bold")
|
|
133
|
+
summary.add_column("Count", justify="right")
|
|
134
|
+
summary.add_row("Errors", str(len(errors)), style="red" if errors else "green")
|
|
135
|
+
summary.add_row("Warnings", str(len(warnings)), style="yellow" if warnings else "green")
|
|
136
|
+
console.print()
|
|
137
|
+
console.print(summary)
|
|
138
|
+
|
|
139
|
+
def dump(name, rows, style):
|
|
140
|
+
if not rows:
|
|
141
|
+
return
|
|
142
|
+
tbl = Table(title=name, box=box.SQUARE, header_style=style, show_lines=False, expand=False)
|
|
143
|
+
tbl.add_column("Line", justify="right", style=style)
|
|
144
|
+
tbl.add_column("Message", style=style, overflow="fold")
|
|
145
|
+
|
|
146
|
+
for line_no, msg in rows:
|
|
147
|
+
tbl.add_row(str(line_no), escape(msg))
|
|
148
|
+
console.print()
|
|
149
|
+
console.print(tbl)
|
|
150
|
+
|
|
151
|
+
dump("Errors", errors, "red")
|
|
152
|
+
dump("Warnings", warnings, "yellow")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@click.command(context_settings={"max_content_width": 120})
|
|
156
|
+
@click.option(
|
|
157
|
+
"--annotations-file",
|
|
158
|
+
"-a",
|
|
159
|
+
required=True,
|
|
160
|
+
type=click.Path(exists=True, dir_okay=False, file_okay=True, path_type=Path, resolve_path=True),
|
|
161
|
+
help="CSV with image_path and plate_text columns.",
|
|
162
|
+
)
|
|
163
|
+
@click.option(
|
|
164
|
+
"--plate-config-file",
|
|
165
|
+
required=True,
|
|
166
|
+
type=click.Path(exists=True, dir_okay=False, file_okay=True, path_type=Path),
|
|
167
|
+
help="Path to the OCR YAML config with image dimensions & alphabet.",
|
|
168
|
+
)
|
|
169
|
+
@click.option(
|
|
170
|
+
"--warn-only",
|
|
171
|
+
is_flag=True,
|
|
172
|
+
help="Exit 0 even if errors occur.",
|
|
173
|
+
)
|
|
174
|
+
@click.option(
|
|
175
|
+
"--export-fixed",
|
|
176
|
+
type=str,
|
|
177
|
+
help="Filename for the cleaned CSV written in the same directory as the annotations file.",
|
|
178
|
+
)
|
|
179
|
+
@click.option(
|
|
180
|
+
"--min-height",
|
|
181
|
+
default=2,
|
|
182
|
+
show_default=True,
|
|
183
|
+
type=int,
|
|
184
|
+
help="Minimum allowed image height.",
|
|
185
|
+
)
|
|
186
|
+
@click.option(
|
|
187
|
+
"--min-width",
|
|
188
|
+
default=2,
|
|
189
|
+
show_default=True,
|
|
190
|
+
type=int,
|
|
191
|
+
help="Minimum allowed image width.",
|
|
192
|
+
)
|
|
193
|
+
def validate_dataset(
|
|
194
|
+
annotations_file: Path,
|
|
195
|
+
plate_config_file: Path,
|
|
196
|
+
warn_only: bool,
|
|
197
|
+
export_fixed: Optional[str],
|
|
198
|
+
min_height: int,
|
|
199
|
+
min_width: int,
|
|
200
|
+
):
|
|
201
|
+
"""
|
|
202
|
+
Script to validate the dataset before training.
|
|
203
|
+
"""
|
|
204
|
+
cfg = load_plate_config_from_yaml(plate_config_file)
|
|
205
|
+
|
|
206
|
+
df_annots = pd.read_csv(annotations_file)
|
|
207
|
+
csv_root = annotations_file.parent
|
|
208
|
+
df_annots["image_path"] = df_annots["image_path"].apply(lambda p: str((csv_root / p).resolve()))
|
|
209
|
+
|
|
210
|
+
errors, warnings, cleaned = _validate_dataset(df_annots, cfg, min_height, min_width)
|
|
211
|
+
|
|
212
|
+
# Make cleaned dataset img_path relative (expected format)
|
|
213
|
+
cleaned["image_path"] = cleaned["image_path"].apply(
|
|
214
|
+
lambda p: str(Path(p).relative_to(csv_root))
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
rich_report(errors, warnings)
|
|
218
|
+
|
|
219
|
+
if export_fixed:
|
|
220
|
+
export_path = csv_root / Path(export_fixed).name
|
|
221
|
+
if export_path.resolve() == annotations_file.resolve():
|
|
222
|
+
console.print(
|
|
223
|
+
"[yellow]⚠️ Skipping export: make sure you don't "
|
|
224
|
+
"overwrite original annotations file.[/]"
|
|
225
|
+
)
|
|
226
|
+
elif export_path.exists():
|
|
227
|
+
console.print(f"[yellow]⚠️ Skipping export: file already exists at {export_path}[/]")
|
|
228
|
+
else:
|
|
229
|
+
cleaned.to_csv(export_path, index=False)
|
|
230
|
+
console.print(
|
|
231
|
+
f"[green]✅ Wrote cleaned CSV with {len(cleaned)} rows at {export_path} [/]"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if errors and not warn_only:
|
|
235
|
+
sys.exit(1)
|
|
236
|
+
sys.exit(0)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == "__main__":
|
|
240
|
+
validate_dataset()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Script to visualize the augmented plates used during training.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import pathlib
|
|
8
|
+
import random
|
|
9
|
+
from math import ceil
|
|
10
|
+
from typing import Optional
|
|
11
|
+
import albumentations as A
|
|
12
|
+
import click
|
|
13
|
+
import matplotlib.pyplot as plt
|
|
14
|
+
import numpy as np
|
|
15
|
+
import numpy.typing as npt
|
|
16
|
+
|
|
17
|
+
from fast_plate_ocr.train.data.augmentation import (
|
|
18
|
+
default_train_augmentation,
|
|
19
|
+
)
|
|
20
|
+
from fast_plate_ocr.train.model.config import PlateOCRConfig, load_plate_config_from_yaml
|
|
21
|
+
from fast_plate_ocr.train.utilities import utils
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _set_seed(seed: Optional[int] = None) -> None:
|
|
25
|
+
"""Set random seed for reproducing augmentations."""
|
|
26
|
+
if seed:
|
|
27
|
+
random.seed(seed)
|
|
28
|
+
np.random.seed(seed)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load_images(
|
|
32
|
+
img_dir: pathlib.Path,
|
|
33
|
+
num_images: int,
|
|
34
|
+
shuffle: bool,
|
|
35
|
+
plate_config: PlateOCRConfig,
|
|
36
|
+
augmentation: A.Compose,
|
|
37
|
+
) -> tuple[list[npt.NDArray[np.uint8]], list[npt.NDArray[np.uint8]]]:
|
|
38
|
+
images = list(
|
|
39
|
+
utils.load_images_from_folder(
|
|
40
|
+
img_dir,
|
|
41
|
+
width=plate_config.img_width,
|
|
42
|
+
height=plate_config.img_height,
|
|
43
|
+
image_color_mode=plate_config.image_color_mode,
|
|
44
|
+
keep_aspect_ratio=plate_config.keep_aspect_ratio,
|
|
45
|
+
interpolation_method=plate_config.interpolation,
|
|
46
|
+
padding_color=plate_config.padding_color,
|
|
47
|
+
shuffle=shuffle,
|
|
48
|
+
limit=num_images,
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
augmented_images = [augmentation(image=i)["image"] for i in images]
|
|
52
|
+
return images, augmented_images
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def display_images(
|
|
56
|
+
images: list[npt.NDArray[np.uint8]],
|
|
57
|
+
augmented_images: list[npt.NDArray[np.uint8]],
|
|
58
|
+
columns: int,
|
|
59
|
+
rows: int,
|
|
60
|
+
show_original: bool,
|
|
61
|
+
) -> None:
|
|
62
|
+
num_images = len(images)
|
|
63
|
+
total_plots = rows * columns
|
|
64
|
+
num_pages = ceil(num_images / total_plots)
|
|
65
|
+
for page in range(num_pages):
|
|
66
|
+
_, axs = plt.subplots(rows, columns, figsize=(8, 8))
|
|
67
|
+
axs = axs.flatten()
|
|
68
|
+
for i, ax in enumerate(axs):
|
|
69
|
+
idx = page * total_plots + i
|
|
70
|
+
if idx < num_images:
|
|
71
|
+
if show_original:
|
|
72
|
+
img_to_show = np.concatenate((images[idx], augmented_images[idx]), axis=1)
|
|
73
|
+
else:
|
|
74
|
+
img_to_show = augmented_images[idx]
|
|
75
|
+
ax.imshow(img_to_show, cmap="gray")
|
|
76
|
+
ax.axis("off")
|
|
77
|
+
else:
|
|
78
|
+
ax.axis("off")
|
|
79
|
+
plt.tight_layout()
|
|
80
|
+
plt.show()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ruff: noqa: PLR0913
|
|
84
|
+
# pylint: disable=too-many-arguments,too-many-locals
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@click.command(context_settings={"max_content_width": 120})
|
|
88
|
+
@click.option(
|
|
89
|
+
"--img-dir",
|
|
90
|
+
"-d",
|
|
91
|
+
required=True,
|
|
92
|
+
type=click.Path(exists=True, dir_okay=True, path_type=pathlib.Path),
|
|
93
|
+
help="Path to the images that will be augmented and visualized.",
|
|
94
|
+
)
|
|
95
|
+
@click.option(
|
|
96
|
+
"--plate-config-file",
|
|
97
|
+
required=True,
|
|
98
|
+
type=click.Path(exists=True, file_okay=True, path_type=pathlib.Path),
|
|
99
|
+
help="Path pointing to the model license plate OCR config.",
|
|
100
|
+
)
|
|
101
|
+
@click.option(
|
|
102
|
+
"--num-images",
|
|
103
|
+
"-n",
|
|
104
|
+
type=int,
|
|
105
|
+
default=250,
|
|
106
|
+
show_default=True,
|
|
107
|
+
help="Maximum number of images to visualize.",
|
|
108
|
+
)
|
|
109
|
+
@click.option(
|
|
110
|
+
"--augmentation-path",
|
|
111
|
+
type=click.Path(exists=True, file_okay=True, path_type=pathlib.Path),
|
|
112
|
+
help="YAML file pointing to the augmentation pipeline saved with Albumentations.save(...)",
|
|
113
|
+
)
|
|
114
|
+
@click.option(
|
|
115
|
+
"--shuffle",
|
|
116
|
+
"-s",
|
|
117
|
+
is_flag=True,
|
|
118
|
+
default=False,
|
|
119
|
+
help="Whether to shuffle the images before plotting them.",
|
|
120
|
+
)
|
|
121
|
+
@click.option(
|
|
122
|
+
"--columns",
|
|
123
|
+
"-c",
|
|
124
|
+
type=int,
|
|
125
|
+
default=3,
|
|
126
|
+
show_default=True,
|
|
127
|
+
help="Number of columns in the grid layout for displaying images.",
|
|
128
|
+
)
|
|
129
|
+
@click.option(
|
|
130
|
+
"--rows",
|
|
131
|
+
"-r",
|
|
132
|
+
type=int,
|
|
133
|
+
default=4,
|
|
134
|
+
show_default=True,
|
|
135
|
+
help="Number of rows in the grid layout for displaying images.",
|
|
136
|
+
)
|
|
137
|
+
@click.option(
|
|
138
|
+
"--show-original",
|
|
139
|
+
"-o",
|
|
140
|
+
is_flag=True,
|
|
141
|
+
help="Show the original image along with the augmented one.",
|
|
142
|
+
)
|
|
143
|
+
@click.option(
|
|
144
|
+
"--seed",
|
|
145
|
+
type=int,
|
|
146
|
+
help="Seed for reproducing augmentations.",
|
|
147
|
+
)
|
|
148
|
+
def visualize_augmentation(
|
|
149
|
+
img_dir: pathlib.Path,
|
|
150
|
+
plate_config_file: pathlib.Path,
|
|
151
|
+
num_images: int,
|
|
152
|
+
augmentation_path: Optional[pathlib.Path],
|
|
153
|
+
shuffle: bool,
|
|
154
|
+
columns: int,
|
|
155
|
+
rows: int,
|
|
156
|
+
seed: Optional[int],
|
|
157
|
+
show_original: bool,
|
|
158
|
+
) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Visualize augmentation pipeline applied to raw images.
|
|
161
|
+
"""
|
|
162
|
+
_set_seed(seed)
|
|
163
|
+
config = load_plate_config_from_yaml(plate_config_file)
|
|
164
|
+
aug = (
|
|
165
|
+
A.load(augmentation_path, data_format="yaml")
|
|
166
|
+
if augmentation_path
|
|
167
|
+
else default_train_augmentation(config.image_color_mode)
|
|
168
|
+
)
|
|
169
|
+
aug.set_random_seed(seed)
|
|
170
|
+
images, augmented_images = load_images(img_dir, num_images, shuffle, config, aug)
|
|
171
|
+
display_images(images, augmented_images, columns, rows, show_original)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
# pylint: disable = no-value-for-parameter
|
|
176
|
+
visualize_augmentation()
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Script for displaying an image with the OCR model predictions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import pathlib
|
|
9
|
+
from typing import Optional
|
|
10
|
+
import click
|
|
11
|
+
import cv2
|
|
12
|
+
import keras
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from fast_plate_ocr.train.model.config import load_plate_config_from_yaml
|
|
16
|
+
from fast_plate_ocr.train.utilities import utils
|
|
17
|
+
from fast_plate_ocr.train.utilities.utils import postprocess_model_output
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.command(context_settings={"max_content_width": 120})
|
|
22
|
+
@click.option(
|
|
23
|
+
"-m",
|
|
24
|
+
"--model",
|
|
25
|
+
"model_path",
|
|
26
|
+
required=True,
|
|
27
|
+
type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=pathlib.Path),
|
|
28
|
+
help="Path to the saved .keras model.",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--plate-config-file",
|
|
32
|
+
required=True,
|
|
33
|
+
type=click.Path(exists=True, file_okay=True, path_type=pathlib.Path),
|
|
34
|
+
help="Path pointing to the model license plate OCR config.",
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"-d",
|
|
38
|
+
"--img-dir",
|
|
39
|
+
required=True,
|
|
40
|
+
type=click.Path(exists=True, dir_okay=True, file_okay=False, path_type=pathlib.Path),
|
|
41
|
+
help="Directory containing the images to make predictions from.",
|
|
42
|
+
)
|
|
43
|
+
@click.option(
|
|
44
|
+
"-l",
|
|
45
|
+
"--low-conf-thresh",
|
|
46
|
+
type=float,
|
|
47
|
+
default=0.35,
|
|
48
|
+
show_default=True,
|
|
49
|
+
help="Threshold for displaying low confidence characters.",
|
|
50
|
+
)
|
|
51
|
+
@click.option(
|
|
52
|
+
"-f",
|
|
53
|
+
"--filter-conf",
|
|
54
|
+
type=float,
|
|
55
|
+
help="Display plates that any of the plate characters are below this number.",
|
|
56
|
+
)
|
|
57
|
+
def visualize_predictions(
|
|
58
|
+
model_path: pathlib.Path,
|
|
59
|
+
plate_config_file: pathlib.Path,
|
|
60
|
+
img_dir: pathlib.Path,
|
|
61
|
+
low_conf_thresh: float,
|
|
62
|
+
filter_conf: Optional[float],
|
|
63
|
+
):
|
|
64
|
+
"""
|
|
65
|
+
Visualize OCR model predictions on unlabeled data.
|
|
66
|
+
"""
|
|
67
|
+
plate_config = load_plate_config_from_yaml(plate_config_file)
|
|
68
|
+
model = utils.load_keras_model(model_path, plate_config)
|
|
69
|
+
images = utils.load_images_from_folder(
|
|
70
|
+
img_dir,
|
|
71
|
+
width=plate_config.img_width,
|
|
72
|
+
height=plate_config.img_height,
|
|
73
|
+
image_color_mode=plate_config.image_color_mode,
|
|
74
|
+
keep_aspect_ratio=plate_config.keep_aspect_ratio,
|
|
75
|
+
interpolation_method=plate_config.interpolation,
|
|
76
|
+
padding_color=plate_config.padding_color,
|
|
77
|
+
)
|
|
78
|
+
for image in images:
|
|
79
|
+
x = np.expand_dims(image, 0)
|
|
80
|
+
prediction = model(x, training=False)
|
|
81
|
+
prediction = keras.ops.stop_gradient(prediction).numpy()
|
|
82
|
+
plate, probs = postprocess_model_output(
|
|
83
|
+
prediction=prediction,
|
|
84
|
+
alphabet=plate_config.alphabet,
|
|
85
|
+
max_plate_slots=plate_config.max_plate_slots,
|
|
86
|
+
vocab_size=plate_config.vocabulary_size,
|
|
87
|
+
)
|
|
88
|
+
if not filter_conf or (filter_conf and np.any(probs < filter_conf)):
|
|
89
|
+
utils.display_predictions(
|
|
90
|
+
image=image, plate=plate, probs=probs, low_conf_thresh=low_conf_thresh
|
|
91
|
+
)
|
|
92
|
+
cv2.destroyAllWindows()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
visualize_predictions()
|