clean
Browse files
app.py
CHANGED
|
@@ -1,16 +1,3 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Engine Part CV System β v5
|
| 3 |
-
ββββββββββββββββββββββββββ
|
| 4 |
-
Key upgrades over v4:
|
| 5 |
-
1. PCA projection β compresses 1536-D CNN space to N most discriminative
|
| 6 |
-
dimensions, so cosine gaps widen from 0.007 β 0.1+
|
| 7 |
-
2. Anomaly scoring β primary signal is "distance from Perfect centroid"
|
| 8 |
-
rather than multi-class cosine race
|
| 9 |
-
3. Per-dim variance weighting (whitening) β equalises feature scales
|
| 10 |
-
4. Mahalanobis-style distance β accounts for within-class spread per axis
|
| 11 |
-
5. Gradio 6.0 fix β theme/css moved to launch()
|
| 12 |
-
"""
|
| 13 |
-
|
| 14 |
import gradio as gr
|
| 15 |
import cv2
|
| 16 |
import numpy as np
|
|
@@ -35,8 +22,8 @@ CLUSTER_VERSION = "v5"
|
|
| 35 |
TEXTURE_WEIGHT = 1.6
|
| 36 |
MIN_SAMPLES_WARN = 5
|
| 37 |
MIN_MATCH_SAMPLES= 3
|
| 38 |
-
PCA_COMPONENTS = 64
|
| 39 |
-
ANOMALY_THRESHOLD= 2.5
|
| 40 |
PERFECT_CLASS = "Perfect"
|
| 41 |
|
| 42 |
|
|
@@ -192,20 +179,7 @@ class FeatureExtractor:
|
|
| 192 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 193 |
|
| 194 |
class PCAProjector:
|
| 195 |
-
|
| 196 |
-
Fits a PCA on ALL stored feature vectors across ALL classes, then
|
| 197 |
-
projects every query into the lower-dimensional discriminative subspace.
|
| 198 |
-
|
| 199 |
-
Why this fixes the 0.9944 / 0.9865 cosine collapse
|
| 200 |
-
βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 201 |
-
In 1536-D space virtually every unit vector has cosine similarity β₯ 0.98
|
| 202 |
-
to every other β this is the "curse of dimensionality". PCA finds the
|
| 203 |
-
axes of maximum variance in the training data. These axes correspond
|
| 204 |
-
to the visual differences BETWEEN classes (colour, texture, defect edges).
|
| 205 |
-
After projecting to 64-D the cosine gap between Perfect and Defected
|
| 206 |
-
typically widens from 0.007 β 0.10β0.30, making classification reliable.
|
| 207 |
-
"""
|
| 208 |
-
|
| 209 |
def __init__(self, n_components: int = PCA_COMPONENTS):
|
| 210 |
self.n_components = n_components
|
| 211 |
self.pca = None
|
|
@@ -393,11 +367,7 @@ class EnginePartDetector:
|
|
| 393 |
return float(np.dot(a,b)/(na*nb)) if na>1e-8 and nb>1e-8 else 0.
|
| 394 |
|
| 395 |
def _mahalanobis(self, query: np.ndarray, name: str) -> float:
|
| 396 |
-
|
| 397 |
-
Diagonal Mahalanobis distance from query to class centroid.
|
| 398 |
-
Accounts for per-axis spread β features with high variance within
|
| 399 |
-
a class contribute less to the distance score.
|
| 400 |
-
"""
|
| 401 |
centroid = self.centroids[name]
|
| 402 |
cov_inv = self.class_cov_inv.get(name)
|
| 403 |
diff = query - centroid
|
|
@@ -407,11 +377,7 @@ class EnginePartDetector:
|
|
| 407 |
return float(np.linalg.norm(diff))
|
| 408 |
|
| 409 |
def _anomaly_score(self, query_proj: np.ndarray) -> dict:
|
| 410 |
-
|
| 411 |
-
Primary decision signal: z-score distance from the Perfect centroid.
|
| 412 |
-
Lower = more like a Perfect part.
|
| 413 |
-
Returns dict with anomaly_z, perfect_dist, verdict.
|
| 414 |
-
"""
|
| 415 |
if PERFECT_CLASS not in self.centroids:
|
| 416 |
return {"anomaly_z": None, "verdict": "no_perfect_class"}
|
| 417 |
|
|
@@ -815,10 +781,6 @@ with gr.Blocks(title="Engine Part CV System v5") as demo:
|
|
| 815 |
rst_btn.click(reset_all_ui, [], [rst_st, lib_txt, lib_roi])
|
| 816 |
demo.load(update_library_preview, [], [lib_txt, lib_roi])
|
| 817 |
|
| 818 |
-
gr.Markdown("""<div class="footer">
|
| 819 |
-
Engine Part CV System v5 β’ PCA + Anomaly Scoring + Centroid Cosine
|
| 820 |
-
</div>""")
|
| 821 |
-
|
| 822 |
|
| 823 |
if __name__ == "__main__":
|
| 824 |
demo.launch(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import cv2
|
| 3 |
import numpy as np
|
|
|
|
| 22 |
TEXTURE_WEIGHT = 1.6
|
| 23 |
MIN_SAMPLES_WARN = 5
|
| 24 |
MIN_MATCH_SAMPLES= 3
|
| 25 |
+
PCA_COMPONENTS = 64
|
| 26 |
+
ANOMALY_THRESHOLD= 2.5
|
| 27 |
PERFECT_CLASS = "Perfect"
|
| 28 |
|
| 29 |
|
|
|
|
| 179 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 180 |
|
| 181 |
class PCAProjector:
|
| 182 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
def __init__(self, n_components: int = PCA_COMPONENTS):
|
| 184 |
self.n_components = n_components
|
| 185 |
self.pca = None
|
|
|
|
| 367 |
return float(np.dot(a,b)/(na*nb)) if na>1e-8 and nb>1e-8 else 0.
|
| 368 |
|
| 369 |
def _mahalanobis(self, query: np.ndarray, name: str) -> float:
|
| 370 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
centroid = self.centroids[name]
|
| 372 |
cov_inv = self.class_cov_inv.get(name)
|
| 373 |
diff = query - centroid
|
|
|
|
| 377 |
return float(np.linalg.norm(diff))
|
| 378 |
|
| 379 |
def _anomaly_score(self, query_proj: np.ndarray) -> dict:
|
| 380 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
if PERFECT_CLASS not in self.centroids:
|
| 382 |
return {"anomaly_z": None, "verdict": "no_perfect_class"}
|
| 383 |
|
|
|
|
| 781 |
rst_btn.click(reset_all_ui, [], [rst_st, lib_txt, lib_roi])
|
| 782 |
demo.load(update_library_preview, [], [lib_txt, lib_roi])
|
| 783 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 784 |
|
| 785 |
if __name__ == "__main__":
|
| 786 |
demo.launch(
|