DrAbbas commited on
Commit
5bc9da1
ยท
verified ยท
1 Parent(s): 96d1d29

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +9 -391
app.py CHANGED
@@ -624,305 +624,6 @@ def draw_arabic_boxes(img, regions):
624
  draw.text((x+5, y+2), txt, fill=(255,255,255), font=font)
625
  return img
626
 
627
- # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
628
- # ๐Ÿ”ฌ Physics-Based Density Anomaly Detection (PBDAD)
629
- # ูƒุดู ุงู„ุจุถุงุฆุน ุงู„ู…ุฎููŠุฉ ุจุงู„ุชุญู„ูŠู„ ุงู„ููŠุฒูŠุงุฆูŠ โ€” ุจุฏูˆู† ุชุฏุฑูŠุจ
630
- # ูŠุนู…ู„ ุญุชู‰ ู„ูˆ ุงู„ู…ู‡ุฑู‘ุจ ุฃุฎูู‰ ุงู„ุฃุฏูˆูŠุฉ ุจูŠู† ุจุถุงุฆุน ุฃุฎุฑู‰
631
- # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
632
-
633
- # ุงู„ุจุตู…ุฉ ุงู„ููŠุฒูŠุงุฆูŠุฉ ู„ู„ุฃุฏูˆูŠุฉ ุงู„ุจุดุฑูŠุฉ (ู…ุณุชุฎุฑุฌุฉ ู…ู† 7 ุตูˆุฑ ุญู‚ูŠู‚ูŠุฉ)
634
- PHARMA_SIGNATURE = {
635
- 'mean_range': (90, 145),
636
- 'dark_ratio_range': (0.12, 0.50),
637
- 'entropy_range': (7.0, 7.7),
638
- 'std_range': (40, 70),
639
- }
640
-
641
- # ุจุตู…ุงุช ุจุถุงุฆุน ู…ุนุฑูˆูุฉ (ู„ู„ู…ู‚ุงุฑู†ุฉ)
642
- MATERIAL_SIGNATURES = {
643
- 'pharmaceutical': {'mean': (90, 145), 'dark': (0.12, 0.50), 'entropy': (7.0, 7.7), 'label_ar': 'ุฃุฏูˆูŠุฉ ุจุดุฑูŠุฉ'},
644
- 'bags': {'mean': (140, 180), 'dark': (0.01, 0.10), 'entropy': (6.5, 7.3), 'label_ar': 'ุญู‚ุงุฆุจ'},
645
- 'metal': {'mean': (40, 90), 'dark': (0.40, 0.80), 'entropy': (6.0, 7.0), 'label_ar': 'ู…ุนุงุฏู†'},
646
- 'food': {'mean': (110, 160), 'dark': (0.05, 0.25), 'entropy': (6.8, 7.5), 'label_ar': 'ู…ูˆุงุฏ ุบุฐุงุฆูŠุฉ'},
647
- 'electronics': {'mean': (100, 150), 'dark': (0.10, 0.35), 'entropy': (7.2, 7.8), 'label_ar': 'ุฅู„ูƒุชุฑูˆู†ูŠุงุช'},
648
- 'empty': {'mean': (170, 230), 'dark': (0.00, 0.05), 'entropy': (4.0, 6.5), 'label_ar': 'ูุงุฑุบ'},
649
- }
650
-
651
-
652
- def extract_region_physics(img_array, gray_region):
653
- """ุงุณุชุฎุฑุงุฌ ุงู„ุฎุตุงุฆุต ุงู„ููŠุฒูŠุงุฆูŠุฉ ู„ู…ู†ุทู‚ุฉ ูˆุงุญุฏุฉ"""
654
- # ุฅุฒุงู„ุฉ ุงู„ุฎู„ููŠุฉ ุงู„ุฒุฑู‚ุงุก ูˆุงู„ุฃุฌุฒุงุก ุงู„ุฏุงูƒู†ุฉ ุฌุฏุงู‹ (ุงู„ุดุงุณูŠ)
655
- if len(img_array.shape) == 3:
656
- blue_mask = (img_array[:,:,2] > 150) & (img_array[:,:,0] < 100) & (img_array[:,:,1] < 100)
657
- else:
658
- blue_mask = np.zeros(gray_region.shape, dtype=bool)
659
-
660
- dark_mask = gray_region < 15
661
- cargo_mask = ~blue_mask & ~dark_mask
662
-
663
- if cargo_mask.sum() < 100:
664
- return None
665
-
666
- cargo = gray_region[cargo_mask]
667
-
668
- # Entropy
669
- hist, _ = np.histogram(cargo, bins=256, range=(0, 255), density=True)
670
- hist = hist[hist > 0]
671
- entropy = -np.sum(hist * np.log2(hist)) if len(hist) > 0 else 0
672
-
673
- # Texture (local variance)
674
- block = 8
675
- h, w = gray_region.shape
676
- variances = []
677
- for y in range(0, h - block, block):
678
- for x in range(0, w - block, block):
679
- b = gray_region[y:y+block, x:x+block]
680
- if cargo_mask[y:y+block, x:x+block].sum() > block:
681
- variances.append(b.var())
682
- texture_var = np.mean(variances) if variances else 0
683
-
684
- # RGB means (if color)
685
- r_mean, g_mean, b_mean = 0, 0, 0
686
- if len(img_array.shape) == 3:
687
- cargo_rgb = img_array[cargo_mask]
688
- r_mean = cargo_rgb[:, 0].mean()
689
- g_mean = cargo_rgb[:, 1].mean()
690
- b_mean = cargo_rgb[:, 2].mean()
691
-
692
- return {
693
- 'mean': cargo.mean(),
694
- 'std': cargo.std(),
695
- 'median': np.median(cargo),
696
- 'dark_ratio': (cargo < 80).sum() / len(cargo),
697
- 'mid_ratio': ((cargo >= 80) & (cargo < 160)).sum() / len(cargo),
698
- 'bright_ratio': (cargo >= 160).sum() / len(cargo),
699
- 'entropy': entropy,
700
- 'texture_var': texture_var,
701
- 'r_mean': r_mean,
702
- 'g_mean': g_mean,
703
- 'b_mean': b_mean,
704
- 'pixel_count': len(cargo),
705
- }
706
-
707
-
708
- def match_material_signature(physics):
709
- """ู…ุทุงุจู‚ุฉ ุงู„ุฎุตุงุฆุต ุงู„ููŠุฒูŠุงุฆูŠุฉ ู…ุน ุจุตู…ุงุช ุงู„ู…ูˆุงุฏ ุงู„ู…ุนุฑูˆูุฉ"""
710
- if physics is None:
711
- return 'unknown', 0.0, 'ุบูŠุฑ ู…ุญุฏุฏ'
712
-
713
- scores = {}
714
- for material, sig in MATERIAL_SIGNATURES.items():
715
- score = 0
716
- total = 0
717
-
718
- # Mean intensity match
719
- if sig['mean'][0] <= physics['mean'] <= sig['mean'][1]:
720
- score += 3
721
- elif abs(physics['mean'] - np.mean(sig['mean'])) < 30:
722
- score += 1
723
- total += 3
724
-
725
- # Dark ratio match
726
- if sig['dark'][0] <= physics['dark_ratio'] <= sig['dark'][1]:
727
- score += 3
728
- elif abs(physics['dark_ratio'] - np.mean(sig['dark'])) < 0.15:
729
- score += 1
730
- total += 3
731
-
732
- # Entropy match
733
- if sig['entropy'][0] <= physics['entropy'] <= sig['entropy'][1]:
734
- score += 2
735
- total += 2
736
-
737
- scores[material] = score / total
738
-
739
- best = max(scores, key=scores.get)
740
- return best, scores[best], MATERIAL_SIGNATURES[best]['label_ar']
741
-
742
-
743
- def physics_density_scan(img, n_regions=8):
744
- """
745
- ๐Ÿ”ฌ ุงู„ู…ุณุญ ุงู„ููŠุฒูŠุงุฆูŠ ุงู„ุดุงู…ู„ โ€” ูƒุดู ุงู„ุจุถุงุฆุน ุงู„ู…ุฎููŠุฉ
746
- ูŠู‚ุณู… ุงู„ุญุงูˆูŠุฉ ู„ู…ู†ุงุทู‚ ูˆูŠุญู„ู„ ูƒุซุงูุฉ ูƒู„ ู…ู†ุทู‚ุฉ
747
- ูŠูƒุดู ุงู„ุงุฎุชู„ุงูุงุช ุญุชู‰ ู„ูˆ ุงู„ู…ู‡ุฑู‘ุจ ุฃุฎูู‰ ุงู„ุฃุฏูˆูŠุฉ ุจุฐูƒุงุก
748
- """
749
- if img is None:
750
- return [], {}, None
751
-
752
- arr = np.array(img.convert('RGB'))
753
- gray = np.dot(arr[...,:3], [0.299, 0.587, 0.114])
754
- h, w = gray.shape
755
- region_w = w // n_regions
756
-
757
- regions = []
758
- pos_names_8 = ["ู…ู‚ุฏู…ุฉ", "ุฑุจุน-1", "ุฑุจุน-2", "ูˆุณุท-ุฃ", "ูˆุณุท-ุจ", "ุฑุจุน-3", "ุฑุจุน-4", "ู†ู‡ุงูŠุฉ"]
759
- pos_names_4 = ["ุงู„ู…ู‚ุฏู…ุฉ", "ูˆุณุท-1", "ูˆุณุท-2", "ุงู„ู†ู‡ุงูŠุฉ"]
760
- pos_names = pos_names_8 if n_regions == 8 else (pos_names_4 if n_regions == 4 else [f"ู‚ุณู…-{i+1}" for i in range(n_regions)])
761
-
762
- for i in range(n_regions):
763
- x1 = i * region_w
764
- x2 = min(x1 + region_w, w)
765
-
766
- region_arr = arr[:, x1:x2, :]
767
- region_gray = gray[:, x1:x2]
768
-
769
- physics = extract_region_physics(region_arr, region_gray)
770
- if physics is None:
771
- continue
772
-
773
- material, match_score, material_ar = match_material_signature(physics)
774
-
775
- regions.append({
776
- 'idx': i,
777
- 'position': pos_names[i] if i < len(pos_names) else f"ู‚ุณู…-{i+1}",
778
- 'bbox': (x1, 0, x2 - x1, h),
779
- 'physics': physics,
780
- 'material': material,
781
- 'material_ar': material_ar,
782
- 'match_score': match_score,
783
- })
784
-
785
- # โ•โ•โ• ุชุญู„ูŠู„ ุงู„ุงุฎุชู„ุงูุงุช ุจูŠู† ุงู„ู…ู†ุงุทู‚ โ•โ•โ•
786
- if len(regions) < 2:
787
- return regions, {}, None
788
-
789
- means = [r['physics']['mean'] for r in regions]
790
- darks = [r['physics']['dark_ratio'] for r in regions]
791
- entropies = [r['physics']['entropy'] for r in regions]
792
-
793
- overall_mean = np.mean(means)
794
- overall_dark = np.mean(darks)
795
- mean_std = np.std(means) # ุชุจุงูŠู† ุงู„ูƒุซุงูุฉ ุจูŠู† ุงู„ู…ู†ุงุทู‚
796
- dark_std = np.std(darks)
797
-
798
- # โœ… ู‡ู„ ุงู„ุญุงูˆูŠุฉ ู…ุชุฌุงู†ุณุฉุŸ (ูƒุซุงูุฉ ู…ูˆุญุฏุฉ)
799
- is_uniform = mean_std < 15 and dark_std < 0.08
800
-
801
- anomalies = {}
802
- for r in regions:
803
- p = r['physics']
804
- mean_diff = abs(p['mean'] - overall_mean)
805
- dark_diff = abs(p['dark_ratio'] - overall_dark)
806
-
807
- # โ•โ•โ• ู‚ูˆุงุนุฏ ุงู„ูƒุดู โ€” ู†ุณุจูŠุฉ ูˆู„ูŠุณุช ู…ุทู„ู‚ุฉ โ•โ•โ•
808
-
809
- # 1. ูุฑู‚ ุงู„ูƒุซุงูุฉ ุนู† ุงู„ู…ุชูˆุณุท (ู†ุณุจูŠ ู„ู‡ุฐู‡ ุงู„ุญุงูˆูŠุฉ)
810
- density_anomaly = mean_diff > max(25, mean_std * 1.5)
811
-
812
- # 2. ูุฑู‚ ุงู„ุจูƒุณู„ุงุช ุงู„ุฏุงูƒู†ุฉ (ู†ุณุจูŠ)
813
- dark_anomaly = dark_diff > max(0.12, dark_std * 1.5)
814
-
815
- # 3. ู…ุทุงุจู‚ุฉ ุจุตู…ุฉ ุงู„ุฃุฏูˆูŠุฉ โ€” ูู‚ุท ุฅุฐุง ุงู„ู…ู†ุทู‚ุฉ ู…ุฎุชู„ูุฉ ุนู† ุงู„ุจุงู‚ูŠ!
816
- # โœ… FIX: ู„ุง ุชุญูƒู… "ุฃุฏูˆูŠุฉ" ุฅุฐุง ูƒู„ ุงู„ุญุงูˆูŠุฉ ู…ุชุดุงุจู‡ุฉ
817
- pharma_range_match = (
818
- PHARMA_SIGNATURE['mean_range'][0] <= p['mean'] <= PHARMA_SIGNATURE['mean_range'][1] and
819
- PHARMA_SIGNATURE['dark_ratio_range'][0] <= p['dark_ratio'] <= PHARMA_SIGNATURE['dark_ratio_range'][1] and
820
- PHARMA_SIGNATURE['entropy_range'][0] <= p['entropy'] <= PHARMA_SIGNATURE['entropy_range'][1]
821
- )
822
- # ุจุตู…ุฉ ุงู„ุฃุฏูˆูŠุฉ = ู…ุทุงุจู‚ุฉ ุงู„ู†ุทุงู‚ + ุงุฎุชู„ุงู ุนู† ุจู‚ูŠุฉ ุงู„ุญุงูˆูŠุฉ
823
- is_pharma_match = pharma_range_match and (density_anomaly or dark_anomaly) and not is_uniform
824
-
825
- # ุงู„ู†ุชูŠุฌุฉ ุงู„ู…ุฑูƒู‘ุจุฉ
826
- score = 0
827
- reasons = []
828
- if density_anomaly:
829
- score += 35
830
- reasons.append(f"ูƒุซุงูุฉ ู…ุฎุชู„ูุฉ ({mean_diff:.0f})")
831
- if dark_anomaly:
832
- score += 30
833
- reasons.append(f"ุจูƒุณู„ุงุช ุฏุงูƒู†ุฉ ู…ุฎุชู„ูุฉ ({dark_diff:.1%})")
834
- if is_pharma_match:
835
- score += 35
836
- reasons.append("ู…ุทุงุจู‚ุฉ ุจุตู…ุฉ ุฃุฏูˆูŠุฉ!")
837
-
838
- r['anomaly_score'] = min(score, 100)
839
- r['is_anomaly'] = score >= 50
840
- r['is_pharma_suspect'] = is_pharma_match
841
- r['reasons'] = reasons
842
- r['density_diff'] = mean_diff
843
-
844
- if r['is_anomaly']:
845
- anomalies[r['idx']] = r
846
-
847
- # โ•โ•โ• ุงู„ุชู‚ุฑูŠุฑ ุงู„ุดุงู…ู„ โ•โ•โ•
848
- materials_found = list(set(r['material'] for r in regions))
849
- has_mixed = len(materials_found) > 1
850
- pharma_suspects = [r for r in regions if r.get('is_pharma_suspect', False)]
851
-
852
- summary = {
853
- 'n_regions': len(regions),
854
- 'materials_found': materials_found,
855
- 'has_mixed_cargo': has_mixed,
856
- 'is_uniform': is_uniform,
857
- 'density_variation': mean_std,
858
- 'n_anomalies': len(anomalies),
859
- 'pharma_suspects': len(pharma_suspects),
860
- 'overall_mean': overall_mean,
861
- 'overall_dark': overall_dark,
862
- 'max_density_diff': max(r['density_diff'] for r in regions) if regions else 0,
863
- 'risk_level': 'critical' if pharma_suspects else ('high' if len(anomalies) > 2 else ('medium' if anomalies else 'low')),
864
- }
865
-
866
- # โ•โ•โ• ุฑุณู… ุฎุฑูŠุทุฉ ุงู„ูƒุซุงูุฉ ุนู„ู‰ ุงู„ุตูˆุฑุฉ โ•โ•โ•
867
- annotated = img.copy()
868
- draw = ImageDraw.Draw(annotated)
869
- try:
870
- font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14)
871
- font_sm = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11)
872
- except:
873
- font = ImageFont.load_default()
874
- font_sm = font
875
-
876
- for r in regions:
877
- x, y, rw, rh = r['bbox']
878
-
879
- if r.get('is_pharma_suspect'):
880
- color = (220, 20, 20) # ุฃุญู…ุฑ โ€” ุฃุฏูˆูŠุฉ ู…ุดุชุจู‡ุฉ
881
- border = 5
882
- elif r.get('is_anomaly'):
883
- color = (255, 140, 0) # ุจุฑุชู‚ุงู„ูŠ โ€” ู…ุดุจูˆู‡
884
- border = 4
885
- else:
886
- color = (0, 180, 0) # ุฃุฎุถุฑ โ€” ุทุจูŠุนูŠ
887
- border = 2
888
-
889
- for t in range(border):
890
- draw.rectangle([x+t, y+t, x+rw-t, y+rh-t], outline=color)
891
-
892
- # Label
893
- label = f"{r['position']}: {r['material_ar']}"
894
- lw = len(label) * 7 + 10
895
- draw.rectangle([x, y, x+lw, y+20], fill=color)
896
- draw.text((x+4, y+2), label, fill=(255,255,255), font=font_sm)
897
-
898
- # Score
899
- score_txt = f"{r['anomaly_score']}%"
900
- draw.rectangle([x, y+20, x+50, y+36], fill=color)
901
- draw.text((x+4, y+22), score_txt, fill=(255,255,255), font=font_sm)
902
-
903
- # Pharma alert
904
- if r.get('is_pharma_suspect'):
905
- alert_y = y + rh - 30
906
- draw.rectangle([x, alert_y, x+rw, alert_y+28], fill=(220, 20, 20))
907
- draw.text((x+4, alert_y+4), "PHARMA SUSPECT!", fill=(255,255,0), font=font)
908
-
909
- # Top banner
910
- if pharma_suspects:
911
- banner_color = (180, 0, 0)
912
- banner_text = f"ALERT: {len(pharma_suspects)} regions match pharmaceutical signature!"
913
- elif anomalies:
914
- banner_color = (200, 100, 0)
915
- banner_text = f"WARNING: {len(anomalies)} anomalous regions detected"
916
- else:
917
- banner_color = (0, 130, 0)
918
- banner_text = "CLEAR: Uniform density across container"
919
-
920
- draw.rectangle([0, 0, w, 24], fill=banner_color)
921
- draw.text((10, 3), banner_text, fill=(255, 255, 255), font=font)
922
-
923
- return regions, summary, annotated
924
-
925
-
926
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
927
  # ๐Ÿ†• Region Classification โ€” ุชู‚ุณูŠู… ุงู„ุญุงูˆูŠุฉ ูˆุชุตู†ูŠู ูƒู„ ุฌุฒุก
928
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@@ -1231,11 +932,6 @@ def analyze_image(img, declared_text):
1231
  has_multi_cargo = len(region_categories) > 1
1232
  region_img = draw_region_boxes(img.copy(), region_results) if region_results else None
1233
 
1234
- # ๐Ÿ”ฌ Physics-Based Density Anomaly Detection (PBDAD)
1235
- physics_regions, physics_summary, physics_img = physics_density_scan(img, n_regions=8)
1236
- pharma_suspects = [r for r in physics_regions if r.get('is_pharma_suspect', False)]
1237
- physics_anomalies = [r for r in physics_regions if r.get('is_anomaly', False)]
1238
-
1239
  # โ•โ•โ• Anomaly Detection โ•โ•โ•
1240
  full_feat = extract_features_from_image(img)
1241
  full_anom_score, full_is_anom, full_anom_det = check_anomaly(full_feat)
@@ -1458,77 +1154,6 @@ def analyze_image(img, declared_text):
1458
 
1459
  region_html += "</table></div>"
1460
 
1461
- # ๐Ÿ”ฌ ุนุฑุถ ู†ุชุงุฆุฌ ุงู„ุชุญู„ูŠู„ ุงู„ููŠุฒูŠุงุฆูŠ (PBDAD)
1462
- physics_html = ""
1463
- if physics_regions:
1464
- p_risk = physics_summary.get('risk_level', 'low')
1465
- p_risk_cfg = {
1466
- 'low': ('โœ… ุทุจูŠุนูŠ โ€” ูƒุซุงูุฉ ู…ูˆุญุฏุฉ', '#2E7D32', '#E8F5E9'),
1467
- 'medium': ('โš ๏ธ ูŠูˆุฌุฏ ุงุฎุชู„ุงู ููŠ ุงู„ูƒุซุงูุฉ', '#EF6C00', '#FFF3E0'),
1468
- 'high': ('๐Ÿ”ด ู…ู†ุงุทู‚ ู…ุดุจูˆู‡ุฉ โ€” ูƒุซุงูุฉ ุบูŠุฑ ู…ุชุฌุงู†ุณุฉ', '#C62828', '#FFEBEE'),
1469
- 'critical': ('โ›” ุชู†ุจูŠู‡: ู…ุทุงุจู‚ุฉ ุจุตู…ุฉ ุฃุฏูˆูŠุฉ ุจุดุฑูŠุฉ!', '#B71C1C', '#FFCDD2'),
1470
- }
1471
- p_text, p_color, p_bg = p_risk_cfg.get(p_risk, p_risk_cfg['low'])
1472
-
1473
- physics_html = f"<div style='margin-top:8px;background:{p_bg};padding:12px;border-radius:10px;border:2px solid {p_color};'>"
1474
- physics_html += f"<div style='background:{p_color};color:white;padding:8px 12px;border-radius:8px;text-align:center;font-weight:bold;margin-bottom:8px;font-size:13px;'>๐Ÿ”ฌ ุงู„ุชุญู„ูŠู„ ุงู„ููŠุฒูŠุงุฆูŠ ู„ู„ูƒุซุงูุฉ (PBDAD) โ€” {p_text}</div>"
1475
-
1476
- # ู…ู„ุฎุต ุฃุฑู‚ุงู…
1477
- is_uniform = physics_summary.get('is_uniform', False)
1478
- density_var = physics_summary.get('density_variation', 0)
1479
- uniform_txt = "โœ… ู…ุชุฌุงู†ุณุฉ" if is_uniform else "โš ๏ธ ุบูŠุฑ ู…ุชุฌุงู†ุณุฉ"
1480
- uniform_clr = "#2E7D32" if is_uniform else "#E65100"
1481
-
1482
- physics_html += f"<div style='display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap;justify-content:center;'>"
1483
- physics_html += f"<div style='background:white;padding:6px 12px;border-radius:8px;border:1px solid #ddd;text-align:center;'><div style='font-size:18px;font-weight:bold;color:#1565C0;'>{physics_summary.get('n_regions',0)}</div><div style='font-size:10px;color:#777;'>ู…ู†ุงุทู‚</div></div>"
1484
- physics_html += f"<div style='background:white;padding:6px 12px;border-radius:8px;border:1px solid {uniform_clr};text-align:center;'><div style='font-size:14px;font-weight:bold;color:{uniform_clr};'>{uniform_txt}</div><div style='font-size:10px;color:#777;'>ุชุจุงูŠู†: {density_var:.1f}</div></div>"
1485
- physics_html += f"<div style='background:white;padding:6px 12px;border-radius:8px;border:1px solid #ddd;text-align:center;'><div style='font-size:18px;font-weight:bold;color:#E65100;'>{len(physics_anomalies)}</div><div style='font-size:10px;color:#777;'>ู…ุดุจูˆู‡ุฉ</div></div>"
1486
- physics_html += f"<div style='background:white;padding:6px 12px;border-radius:8px;border:1px solid #ddd;text-align:center;'><div style='font-size:18px;font-weight:bold;color:#C62828;'>{len(pharma_suspects)}</div><div style='font-size:10px;color:#777;'>ุจุตู…ุฉ ุฃุฏูˆูŠุฉ</div></div>"
1487
- physics_html += f"<div style='background:white;padding:6px 12px;border-radius:8px;border:1px solid #ddd;text-align:center;'><div style='font-size:18px;font-weight:bold;color:#6A1B9A;'>{physics_summary.get('max_density_diff',0):.0f}</div><div style='font-size:10px;color:#777;'>ูุฑู‚ ูƒุซุงูุฉ</div></div>"
1488
- physics_html += "</div>"
1489
-
1490
- # ุฌุฏูˆู„ ุงู„ู…ู†ุงุทู‚
1491
- physics_html += "<table style='width:100%;border-collapse:collapse;font-size:11px;'>"
1492
- physics_html += "<tr style='background:#37474F;color:white;'><th style='padding:5px;'>ุงู„ู…ู†ุทู‚ุฉ</th><th style='padding:5px;'>ุงู„ู…ุงุฏุฉ ุงู„ู…ุญุชู…ู„ุฉ</th><th style='padding:5px;'>ุงู„ูƒุซุงูุฉ</th><th style='padding:5px;'>ุงู„ุฏุงูƒู†%</th><th style='padding:5px;'>Entropy</th><th style='padding:5px;'>ุงู„ู†ุชูŠุฌุฉ</th></tr>"
1493
-
1494
- for r in physics_regions:
1495
- p = r['physics']
1496
- is_pharma = r.get('is_pharma_suspect', False)
1497
- is_anom = r.get('is_anomaly', False)
1498
-
1499
- if is_pharma:
1500
- row_bg = "#FFCDD2"
1501
- status = f"โ›” {r['anomaly_score']}%"
1502
- elif is_anom:
1503
- row_bg = "#FFF3E0"
1504
- status = f"โš ๏ธ {r['anomaly_score']}%"
1505
- else:
1506
- row_bg = "#E8F5E9"
1507
- status = f"โœ… {r['anomaly_score']}%"
1508
-
1509
- physics_html += f"<tr style='background:{row_bg};border-bottom:1px solid #ddd;'>"
1510
- physics_html += f"<td style='padding:4px 6px;font-weight:bold;'>๐Ÿ“ {r['position']}</td>"
1511
- physics_html += f"<td style='padding:4px;color:{'#C62828' if is_pharma else '#333'};font-weight:{'bold' if is_pharma else 'normal'};'>{r['material_ar']}{' โ›”' if is_pharma else ''}</td>"
1512
- physics_html += f"<td style='padding:4px;text-align:center;font-family:monospace;'>{p['mean']:.0f}</td>"
1513
- physics_html += f"<td style='padding:4px;text-align:center;'>{p['dark_ratio']:.0%}</td>"
1514
- physics_html += f"<td style='padding:4px;text-align:center;'>{p['entropy']:.1f}</td>"
1515
- physics_html += f"<td style='padding:4px;text-align:center;font-weight:bold;'>{status}</td>"
1516
- physics_html += "</tr>"
1517
-
1518
- physics_html += "</table>"
1519
-
1520
- # ุชูุงุตูŠู„ ุงู„ู…ู†ุงุทู‚ ุงู„ู…ุดุจูˆู‡ุฉ
1521
- if pharma_suspects or physics_anomalies:
1522
- physics_html += f"<div style='margin-top:6px;padding:8px;background:white;border-radius:8px;border:1px solid {p_color};'>"
1523
- physics_html += f"<div style='font-weight:bold;color:{p_color};margin-bottom:4px;font-size:12px;'>๐Ÿ“‹ ุชูุงุตูŠู„ ุงู„ุงุดุชุจุงู‡:</div>"
1524
- for r in (pharma_suspects + [a for a in physics_anomalies if a not in pharma_suspects]):
1525
- reasons_txt = " | ".join(r.get('reasons', []))
1526
- icon = "โ›”" if r.get('is_pharma_suspect') else "โš ๏ธ"
1527
- physics_html += f"<div style='font-size:11px;padding:2px 0;color:#333;'>{icon} <b>{r['position']}</b>: {reasons_txt}</div>"
1528
- physics_html += "</div>"
1529
-
1530
- physics_html += "</div>"
1531
-
1532
  det_html = ""
1533
  if detections:
1534
  det_html = "<div style='margin-top:6px;display:flex;gap:6px;flex-wrap:wrap;'>"
@@ -1568,7 +1193,6 @@ def analyze_image(img, declared_text):
1568
  {model_html}
1569
  {ai_models_html}
1570
  {region_html}
1571
- {physics_html}
1572
  {det_html}
1573
  {anomaly_html}
1574
  <div style='background:#fff;padding:14px;border-radius:10px;border:1px solid #e0e0e0;margin-top:8px;margin-bottom:8px;'>
@@ -1580,17 +1204,13 @@ def analyze_image(img, declared_text):
1580
  <div style='background:#FFF8E1;padding:8px 12px;border-radius:8px;font-size:13px;color:#333;'>{ar_desc}</div>
1581
  </div>
1582
  </div>"""
1583
- # โœ… ุงุฎุชูŠุงุฑ ุงู„ุตูˆุฑุฉ ุงู„ู…ุฎุฑุฌุฉ โ€” ุงู„ุฃูˆู„ูˆูŠุฉ: ุฃุฏูˆูŠุฉ > ู…ู†ุงุทู‚ > anomaly > detection
1584
- if pharma_suspects and physics_img:
1585
- output_img = physics_img # โ›” ุฃุนู„ู‰ ุฃูˆู„ูˆูŠุฉ: ูƒุดู ุฃุฏูˆูŠุฉ
1586
- elif physics_anomalies and physics_img:
1587
- output_img = physics_img # ๐Ÿ”ฌ ุชุญู„ูŠู„ ููŠุฒูŠุงุฆูŠ ู…ุดุจูˆู‡
1588
- elif has_multi_cargo and region_img:
1589
- output_img = region_img
1590
  elif (suspicious or full_is_anom) and anomaly_img:
1591
  output_img = anomaly_img
1592
  elif region_img:
1593
- output_img = region_img
1594
  else:
1595
  output_img = annotated_img
1596
  return html, df, output_img
@@ -1850,7 +1470,7 @@ with gr.Blocks(title=f"SONAR-AI v{VERSION}") as app:
1850
  d_refresh.click(refresh_dashboard, outputs=[d_stats, d_tbl])
1851
 
1852
  with gr.Tab("๐Ÿ”ฌ ุชุญู„ูŠู„ ุงู„ุตูˆุฑ"):
1853
- gr.HTML("<div style='background:#E3F2FD;padding:10px 14px;border-radius:10px;margin-bottom:8px;border-right:4px solid #1565C0;'><b style='color:#1565C0;'>๐Ÿ“ท Classification + Detection + Physics Density Analysis + HS Codes</b></div>")
1854
  with gr.Row():
1855
  with gr.Column(scale=1):
1856
  a_img = gr.Image(label="๐Ÿ“ท ุตูˆุฑุฉ ุงู„ุฃุดุนุฉ", type="pil", height=200)
@@ -1875,13 +1495,11 @@ with gr.Blocks(title=f"SONAR-AI v{VERSION}") as app:
1875
  btn_contrast = gr.Button("๐Ÿ”†", elem_id="btn_contrast", size="sm", min_width=30)
1876
  btn_bright = gr.Button("โ˜€", elem_id="btn_bright", size="sm", min_width=30)
1877
  a_dec = gr.Textbox(label="๐Ÿ“‹ ุงู„ุจุถุงุนุฉ ุงู„ู…ุตุฑู‘ุญ ุจู‡ุง", placeholder="SHOES + FABRIC + ...", lines=1)
 
1878
  with gr.Column(scale=1):
1879
- a_det = gr.Image(label="๐Ÿ“ ุงู„ุชุญู„ูŠู„ ุงู„ููŠุฒูŠุงุฆูŠ / Detection", type="pil", height=250)
1880
- # โœ… ุฒุฑ ุงู„ุชุญู„ูŠู„ ุจุนุฑุถ ูƒุงู…ู„
1881
- a_btn = gr.Button("๐Ÿ” ุชุญู„ูŠู„ ุดุงู…ู„ โ€” Classification + Detection + Physics Density", variant="primary", size="lg")
1882
- # โœ… ุงู„ู†ุชุงุฆุฌ ุฃุณูู„ ุงู„ุฒุฑ ุจุนุฑุถ ูƒุงู…ู„
1883
- a_res = gr.HTML("<div style='text-align:center;padding:30px;color:#999;direction:rtl;'>๐Ÿ“ท ุงุฑูุน ุตูˆุฑุฉ ู„ู„ุจุฏุก</div>")
1884
- a_tbl = gr.Dataframe(label="๐Ÿ“‹ ุงู„ุฃุตู†ุงู + HS + ุงู„ุฑุณูˆู…", value=pd.DataFrame(), wrap=True)
1885
  a_btn.click(analyze_image, inputs=[a_img, a_dec], outputs=[a_res, a_tbl, a_det], api_name="analyze")
1886
  btn_gray.click(apply_grayscale, inputs=[a_img], outputs=[a_img])
1887
  btn_inv.click(apply_invert, inputs=[a_img], outputs=[a_img])
 
624
  draw.text((x+5, y+2), txt, fill=(255,255,255), font=font)
625
  return img
626
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
628
  # ๐Ÿ†• Region Classification โ€” ุชู‚ุณูŠู… ุงู„ุญุงูˆูŠุฉ ูˆุชุตู†ูŠู ูƒู„ ุฌุฒุก
629
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
 
932
  has_multi_cargo = len(region_categories) > 1
933
  region_img = draw_region_boxes(img.copy(), region_results) if region_results else None
934
 
 
 
 
 
 
935
  # โ•โ•โ• Anomaly Detection โ•โ•โ•
936
  full_feat = extract_features_from_image(img)
937
  full_anom_score, full_is_anom, full_anom_det = check_anomaly(full_feat)
 
1154
 
1155
  region_html += "</table></div>"
1156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1157
  det_html = ""
1158
  if detections:
1159
  det_html = "<div style='margin-top:6px;display:flex;gap:6px;flex-wrap:wrap;'>"
 
1193
  {model_html}
1194
  {ai_models_html}
1195
  {region_html}
 
1196
  {det_html}
1197
  {anomaly_html}
1198
  <div style='background:#fff;padding:14px;border-radius:10px;border:1px solid #e0e0e0;margin-top:8px;margin-bottom:8px;'>
 
1204
  <div style='background:#FFF8E1;padding:8px 12px;border-radius:8px;font-size:13px;color:#333;'>{ar_desc}</div>
1205
  </div>
1206
  </div>"""
1207
+ # โœ… ุงุฎุชูŠุงุฑ ุงู„ุตูˆุฑุฉ ุงู„ู…ุฎุฑุฌุฉ โ€” ุงู„ุฃูˆู„ูˆูŠุฉ: ู…ู†ุงุทู‚ > anomaly > detection
1208
+ if has_multi_cargo and region_img:
1209
+ output_img = region_img # โœ… ุนุฑุถ ู…ุฑุจุนุงุช ุงู„ุฃุตู†ุงู ุงู„ู…ุชุนุฏุฏุฉ
 
 
 
 
1210
  elif (suspicious or full_is_anom) and anomaly_img:
1211
  output_img = anomaly_img
1212
  elif region_img:
1213
+ output_img = region_img # ุนุฑุถ ู…ุฑุจุนุงุช ุงู„ู…ู†ุงุทู‚ ุญุชู‰ ู„ูˆ ุตู†ู ูˆุงุญุฏ
1214
  else:
1215
  output_img = annotated_img
1216
  return html, df, output_img
 
1470
  d_refresh.click(refresh_dashboard, outputs=[d_stats, d_tbl])
1471
 
1472
  with gr.Tab("๐Ÿ”ฌ ุชุญู„ูŠู„ ุงู„ุตูˆุฑ"):
1473
+ gr.HTML("<div style='background:#E3F2FD;padding:10px 14px;border-radius:10px;margin-bottom:8px;border-right:4px solid #1565C0;'><b style='color:#1565C0;'>๐Ÿ“ท Classification + Detection + HS Codes</b></div>")
1474
  with gr.Row():
1475
  with gr.Column(scale=1):
1476
  a_img = gr.Image(label="๐Ÿ“ท ุตูˆุฑุฉ ุงู„ุฃุดุนุฉ", type="pil", height=200)
 
1495
  btn_contrast = gr.Button("๐Ÿ”†", elem_id="btn_contrast", size="sm", min_width=30)
1496
  btn_bright = gr.Button("โ˜€", elem_id="btn_bright", size="sm", min_width=30)
1497
  a_dec = gr.Textbox(label="๐Ÿ“‹ ุงู„ุจุถุงุนุฉ ุงู„ู…ุตุฑู‘ุญ ุจู‡ุง", placeholder="SHOES + FABRIC + ...", lines=1)
1498
+ a_btn = gr.Button("๐Ÿ” ุชุญู„ูŠู„ ุดุงู…ู„", variant="primary", size="lg")
1499
  with gr.Column(scale=1):
1500
+ a_res = gr.HTML("<div style='text-align:center;padding:30px;color:#999;direction:rtl;'>๐Ÿ“ท ุงุฑูุน ุตูˆุฑุฉ ู„ู„ุจุฏุก</div>")
1501
+ a_det = gr.Image(label="๐Ÿ“ Detection / Anomaly", type="pil", height=200)
1502
+ a_tbl = gr.Dataframe(label="๐Ÿ“‹ ุงู„ุฃุตู†ุงู + HS", value=pd.DataFrame(), wrap=True)
 
 
 
1503
  a_btn.click(analyze_image, inputs=[a_img, a_dec], outputs=[a_res, a_tbl, a_det], api_name="analyze")
1504
  btn_gray.click(apply_grayscale, inputs=[a_img], outputs=[a_img])
1505
  btn_inv.click(apply_invert, inputs=[a_img], outputs=[a_img])