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.
Files changed (196) hide show
  1. matrice_analytics/__init__.py +28 -0
  2. matrice_analytics/boundary_drawing_internal/README.md +305 -0
  3. matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
  4. matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
  5. matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
  6. matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
  7. matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
  8. matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
  9. matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
  10. matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
  11. matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
  12. matrice_analytics/post_processing/README.md +455 -0
  13. matrice_analytics/post_processing/__init__.py +732 -0
  14. matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
  15. matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
  16. matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
  17. matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
  18. matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
  19. matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
  20. matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
  21. matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
  22. matrice_analytics/post_processing/config.py +146 -0
  23. matrice_analytics/post_processing/core/__init__.py +63 -0
  24. matrice_analytics/post_processing/core/base.py +704 -0
  25. matrice_analytics/post_processing/core/config.py +3291 -0
  26. matrice_analytics/post_processing/core/config_utils.py +925 -0
  27. matrice_analytics/post_processing/face_reg/__init__.py +43 -0
  28. matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
  29. matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
  30. matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
  31. matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
  32. matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
  33. matrice_analytics/post_processing/ocr/__init__.py +0 -0
  34. matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
  35. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
  36. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
  37. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
  38. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
  39. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
  40. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
  41. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
  42. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
  43. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
  44. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
  45. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
  46. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
  47. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
  48. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
  49. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
  50. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
  51. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
  52. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
  53. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
  54. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
  55. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
  56. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
  57. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
  58. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
  59. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
  60. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
  61. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
  62. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
  63. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
  64. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
  65. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
  66. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
  67. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
  68. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
  69. matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
  70. matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
  71. matrice_analytics/post_processing/post_processor.py +1175 -0
  72. matrice_analytics/post_processing/test_cases/__init__.py +1 -0
  73. matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
  74. matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
  75. matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
  76. matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
  77. matrice_analytics/post_processing/test_cases/test_config.py +852 -0
  78. matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
  79. matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
  80. matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
  81. matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
  82. matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
  83. matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
  84. matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
  85. matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
  86. matrice_analytics/post_processing/usecases/__init__.py +267 -0
  87. matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
  88. matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
  89. matrice_analytics/post_processing/usecases/age_detection.py +842 -0
  90. matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
  91. matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
  92. matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
  93. matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
  94. matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
  95. matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
  96. matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
  97. matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
  98. matrice_analytics/post_processing/usecases/car_service.py +1601 -0
  99. matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
  100. matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
  101. matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
  102. matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
  103. matrice_analytics/post_processing/usecases/color/clip.py +660 -0
  104. matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
  105. matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
  106. matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
  107. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
  108. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
  109. matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
  110. matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
  111. matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
  112. matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
  113. matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
  114. matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
  115. matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
  116. matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
  117. matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
  118. matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
  119. matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
  120. matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
  121. matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
  122. matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
  123. matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
  124. matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
  125. matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
  126. matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
  127. matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
  128. matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
  129. matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
  130. matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
  131. matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
  132. matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
  133. matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
  134. matrice_analytics/post_processing/usecases/leaf.py +821 -0
  135. matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
  136. matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
  137. matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
  138. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
  139. matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
  140. matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
  141. matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
  142. matrice_analytics/post_processing/usecases/parking.py +787 -0
  143. matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
  144. matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
  145. matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
  146. matrice_analytics/post_processing/usecases/people_counting.py +706 -0
  147. matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
  148. matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
  149. matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
  150. matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
  151. matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
  152. matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
  153. matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
  154. matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
  155. matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
  156. matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
  157. matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
  158. matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
  159. matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
  160. matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
  161. matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
  162. matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
  163. matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
  164. matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
  165. matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
  166. matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
  167. matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
  168. matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
  169. matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
  170. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
  171. matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
  172. matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
  173. matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
  174. matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
  175. matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
  176. matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
  177. matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
  178. matrice_analytics/post_processing/utils/__init__.py +150 -0
  179. matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
  180. matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
  181. matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
  182. matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
  183. matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
  184. matrice_analytics/post_processing/utils/color_utils.py +592 -0
  185. matrice_analytics/post_processing/utils/counting_utils.py +182 -0
  186. matrice_analytics/post_processing/utils/filter_utils.py +261 -0
  187. matrice_analytics/post_processing/utils/format_utils.py +293 -0
  188. matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
  189. matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
  190. matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
  191. matrice_analytics/py.typed +0 -0
  192. matrice_analytics-0.1.60.dist-info/METADATA +481 -0
  193. matrice_analytics-0.1.60.dist-info/RECORD +196 -0
  194. matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
  195. matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
  196. 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()
@@ -0,0 +1,3 @@
1
+ from __future__ import annotations
2
+
3
+ """Core utilities and types for Fast Plate OCR (Python 3.8 compatible)."""