Upload 5 files
Browse files- app.py +31 -1
- static/css/style.css +253 -13
- templates/index.html +7 -7
- templates/report.html +145 -128
app.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
import os
|
|
|
|
| 2 |
import zipfile
|
| 3 |
import pandas as pd
|
|
|
|
| 4 |
from flask import Flask, request, redirect, url_for, send_from_directory, flash, render_template
|
| 5 |
from werkzeug.utils import secure_filename
|
| 6 |
from tqdm import tqdm
|
|
@@ -73,6 +75,24 @@ def get_inference_engine():
|
|
| 73 |
print(f"[WARNING] Could not download from HF: {e}. Expecting local files.")
|
| 74 |
|
| 75 |
infer_engine = DiamondInference(model_path, encoder_dir, MODEL_ID)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
return infer_engine
|
| 77 |
|
| 78 |
@app.route('/flush', methods=['POST'])
|
|
@@ -157,11 +177,21 @@ def upload_files():
|
|
| 157 |
y_pred = []
|
| 158 |
|
| 159 |
print(f"[INFO] Initializing Inference Pipeline for {len(df)} stones...")
|
| 160 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
l_code = str(row.get('L_Code', '')).split('.')[0]
|
| 162 |
sr_no = str(row.get('SrNo', '')).split('.')[0]
|
| 163 |
stone_id = str(row.get('Stone_Id', ''))
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
img_path = None
|
| 166 |
for full_path in all_extracted_files:
|
| 167 |
fname = os.path.basename(full_path)
|
|
|
|
| 1 |
import os
|
| 2 |
+
import sys
|
| 3 |
import zipfile
|
| 4 |
import pandas as pd
|
| 5 |
+
import numpy as np
|
| 6 |
from flask import Flask, request, redirect, url_for, send_from_directory, flash, render_template
|
| 7 |
from werkzeug.utils import secure_filename
|
| 8 |
from tqdm import tqdm
|
|
|
|
| 75 |
print(f"[WARNING] Could not download from HF: {e}. Expecting local files.")
|
| 76 |
|
| 77 |
infer_engine = DiamondInference(model_path, encoder_dir, MODEL_ID)
|
| 78 |
+
|
| 79 |
+
# Warmup prediction to initialize TF graph and prevent "stuck" feeling on first stone
|
| 80 |
+
print("[INFO] Warming up Inference Engine...")
|
| 81 |
+
try:
|
| 82 |
+
# Create a dummy row and zero patches for warmup
|
| 83 |
+
dummy_row = {"StoneType": "NATURAL", "Color": "D", "Brown": "N", "BlueUv": "N", "GrdType": "GIA", "Carat": 1.0, "Result": "D"}
|
| 84 |
+
# We don't need a real image for warmup, just a pass through predict
|
| 85 |
+
# We'll mock process_image to return zeros
|
| 86 |
+
orig_process = infer_engine.process_image
|
| 87 |
+
try:
|
| 88 |
+
infer_engine.process_image = lambda path, tta_transform=None: np.zeros(infer_engine.hp["flat_patches_shape"], dtype=np.float32)
|
| 89 |
+
infer_engine.predict(dummy_row, "warmup.jpg", use_tta=False)
|
| 90 |
+
finally:
|
| 91 |
+
infer_engine.process_image = orig_process
|
| 92 |
+
print("[INFO] Warmup complete.")
|
| 93 |
+
except Exception as e:
|
| 94 |
+
print(f"[WARNING] Warmup failed: {e}")
|
| 95 |
+
|
| 96 |
return infer_engine
|
| 97 |
|
| 98 |
@app.route('/flush', methods=['POST'])
|
|
|
|
| 177 |
y_pred = []
|
| 178 |
|
| 179 |
print(f"[INFO] Initializing Inference Pipeline for {len(df)} stones...")
|
| 180 |
+
sys.stdout.flush()
|
| 181 |
+
|
| 182 |
+
# Progress bar with direct stdout for Gunicorn visibility
|
| 183 |
+
pbar = tqdm(df.iterrows(), total=len(df), desc="Inference Progress", file=sys.stdout)
|
| 184 |
+
|
| 185 |
+
for index, row in pbar:
|
| 186 |
l_code = str(row.get('L_Code', '')).split('.')[0]
|
| 187 |
sr_no = str(row.get('SrNo', '')).split('.')[0]
|
| 188 |
stone_id = str(row.get('Stone_Id', ''))
|
| 189 |
|
| 190 |
+
# Log currently processing stone for "aliveness" verification
|
| 191 |
+
if index % 5 == 0:
|
| 192 |
+
print(f"[PROC] Stone {index+1}/{len(df)}: {l_code}")
|
| 193 |
+
sys.stdout.flush()
|
| 194 |
+
|
| 195 |
img_path = None
|
| 196 |
for full_path in all_extracted_files:
|
| 197 |
fname = os.path.basename(full_path)
|
static/css/style.css
CHANGED
|
@@ -51,12 +51,12 @@ canvas#canvas3d {
|
|
| 51 |
position: relative;
|
| 52 |
z-index: 20;
|
| 53 |
width: 100%;
|
| 54 |
-
max-width: 1400px;
|
| 55 |
margin: 0 auto;
|
| 56 |
-
padding: 0.5rem
|
| 57 |
display: flex;
|
| 58 |
flex-direction: column;
|
| 59 |
min-height: 100vh;
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
nav {
|
|
@@ -596,29 +596,269 @@ td {
|
|
| 596 |
}
|
| 597 |
}
|
| 598 |
|
| 599 |
-
/* Dashboard
|
| 600 |
-
.
|
| 601 |
display: grid;
|
| 602 |
grid-template-columns: 1fr;
|
| 603 |
-
gap:
|
|
|
|
| 604 |
}
|
| 605 |
|
| 606 |
@media (min-width: 1100px) {
|
| 607 |
-
.
|
| 608 |
-
grid-template-columns: 1fr
|
|
|
|
| 609 |
}
|
|
|
|
| 610 |
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
}
|
| 616 |
}
|
| 617 |
|
| 618 |
-
.
|
|
|
|
|
|
|
|
|
|
| 619 |
height: 100%;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
display: flex;
|
| 621 |
flex-direction: column;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
}
|
| 623 |
|
| 624 |
.flush-section {
|
|
|
|
| 51 |
position: relative;
|
| 52 |
z-index: 20;
|
| 53 |
width: 100%;
|
|
|
|
| 54 |
margin: 0 auto;
|
| 55 |
+
padding: 0.5rem 1rem;
|
| 56 |
display: flex;
|
| 57 |
flex-direction: column;
|
| 58 |
min-height: 100vh;
|
| 59 |
+
box-sizing: border-box;
|
| 60 |
}
|
| 61 |
|
| 62 |
nav {
|
|
|
|
| 596 |
}
|
| 597 |
}
|
| 598 |
|
| 599 |
+
/* Ultra-Dense Dashboard Grid */
|
| 600 |
+
.dashboard-grid {
|
| 601 |
display: grid;
|
| 602 |
grid-template-columns: 1fr;
|
| 603 |
+
gap: 1rem;
|
| 604 |
+
margin: 0.5rem 0;
|
| 605 |
}
|
| 606 |
|
| 607 |
@media (min-width: 1100px) {
|
| 608 |
+
.dashboard-grid {
|
| 609 |
+
grid-template-columns: 1fr 1fr;
|
| 610 |
+
gap: 1.5rem;
|
| 611 |
}
|
| 612 |
+
}
|
| 613 |
|
| 614 |
+
.dashboard-main {
|
| 615 |
+
display: flex;
|
| 616 |
+
flex-direction: column;
|
| 617 |
+
gap: 1rem;
|
|
|
|
| 618 |
}
|
| 619 |
|
| 620 |
+
.dashboard-sidebar {
|
| 621 |
+
display: flex;
|
| 622 |
+
flex-direction: column;
|
| 623 |
+
gap: 1rem;
|
| 624 |
height: 100%;
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
/* Compact KPI Row */
|
| 628 |
+
.kpi-row {
|
| 629 |
+
display: grid;
|
| 630 |
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
| 631 |
+
gap: 0.8rem;
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
.kpi-card {
|
| 635 |
+
background: rgba(255, 255, 255, 0.05);
|
| 636 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 637 |
+
padding: 1rem 1.2rem;
|
| 638 |
+
text-align: center;
|
| 639 |
+
transition: all 0.3s;
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
.kpi-card:hover {
|
| 643 |
+
background: rgba(255, 255, 255, 0.08);
|
| 644 |
+
border-color: var(--secondary-color);
|
| 645 |
+
}
|
| 646 |
+
|
| 647 |
+
.kpi-value {
|
| 648 |
+
display: block;
|
| 649 |
+
font-size: 1.8rem;
|
| 650 |
+
font-weight: 900;
|
| 651 |
+
color: white;
|
| 652 |
+
line-height: 1;
|
| 653 |
+
margin-bottom: 0.3rem;
|
| 654 |
+
}
|
| 655 |
+
|
| 656 |
+
.kpi-label {
|
| 657 |
+
display: block;
|
| 658 |
+
font-size: 9px;
|
| 659 |
+
letter-spacing: 0.15em;
|
| 660 |
+
text-transform: uppercase;
|
| 661 |
+
opacity: 0.7;
|
| 662 |
+
font-weight: 700;
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
/* Section Labels */
|
| 666 |
+
.section-label {
|
| 667 |
+
font-size: 8px;
|
| 668 |
+
letter-spacing: 0.2em;
|
| 669 |
+
text-transform: uppercase;
|
| 670 |
+
opacity: 0.5;
|
| 671 |
+
font-weight: 700;
|
| 672 |
+
margin-bottom: 0.5rem;
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
/* Compact Table Section */
|
| 676 |
+
.compact-table-section {
|
| 677 |
+
background: rgba(255, 255, 255, 0.03);
|
| 678 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 679 |
+
padding: 1rem;
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
.compact-table-wrapper {
|
| 683 |
+
max-height: 280px;
|
| 684 |
+
overflow-y: auto;
|
| 685 |
+
scrollbar-width: thin;
|
| 686 |
+
scrollbar-color: var(--secondary-color) transparent;
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
.compact-table-wrapper::-webkit-scrollbar {
|
| 690 |
+
width: 3px;
|
| 691 |
+
}
|
| 692 |
+
|
| 693 |
+
.compact-table-wrapper::-webkit-scrollbar-thumb {
|
| 694 |
+
background: var(--secondary-color);
|
| 695 |
+
border-radius: 10px;
|
| 696 |
+
}
|
| 697 |
+
|
| 698 |
+
.compact-table {
|
| 699 |
+
width: 100%;
|
| 700 |
+
border-collapse: collapse;
|
| 701 |
+
font-size: 11px;
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
.compact-table th {
|
| 705 |
+
text-align: left;
|
| 706 |
+
padding: 0.4rem;
|
| 707 |
+
font-size: 8px;
|
| 708 |
+
letter-spacing: 0.1em;
|
| 709 |
+
text-transform: uppercase;
|
| 710 |
+
opacity: 0.5;
|
| 711 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
| 712 |
+
position: sticky;
|
| 713 |
+
top: 0;
|
| 714 |
+
background: rgba(5, 5, 5, 0.95);
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
.compact-table td {
|
| 718 |
+
padding: 0.4rem;
|
| 719 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
| 720 |
+
}
|
| 721 |
+
|
| 722 |
+
.compact-table .grade-col {
|
| 723 |
+
font-weight: 700;
|
| 724 |
+
color: var(--secondary-color);
|
| 725 |
+
}
|
| 726 |
+
|
| 727 |
+
.compact-table .sup-col {
|
| 728 |
+
opacity: 0.4;
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
/* Compact Confusion Matrix */
|
| 732 |
+
.cm-compact {
|
| 733 |
+
background: rgba(255, 255, 255, 0.03);
|
| 734 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 735 |
+
padding: 1rem;
|
| 736 |
display: flex;
|
| 737 |
flex-direction: column;
|
| 738 |
+
height: 100%;
|
| 739 |
+
}
|
| 740 |
+
|
| 741 |
+
.cm-compact-table {
|
| 742 |
+
width: 100%;
|
| 743 |
+
border-collapse: collapse;
|
| 744 |
+
font-size: 8px;
|
| 745 |
+
table-layout: auto;
|
| 746 |
+
}
|
| 747 |
+
|
| 748 |
+
.cm-compact-table th,
|
| 749 |
+
.cm-compact-table td {
|
| 750 |
+
padding: 0.3rem 0.35rem;
|
| 751 |
+
text-align: center;
|
| 752 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 753 |
+
font-size: 8px;
|
| 754 |
+
}
|
| 755 |
+
|
| 756 |
+
.cm-compact-table th {
|
| 757 |
+
font-size: 7px;
|
| 758 |
+
letter-spacing: 0.03em;
|
| 759 |
+
background: rgba(255, 255, 255, 0.02);
|
| 760 |
+
color: var(--secondary-color);
|
| 761 |
+
font-weight: 600;
|
| 762 |
+
}
|
| 763 |
+
|
| 764 |
+
.cm-corner {
|
| 765 |
+
width: 30px;
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
.cm-row-label {
|
| 769 |
+
background: rgba(255, 255, 255, 0.02);
|
| 770 |
+
font-weight: 700;
|
| 771 |
+
color: var(--secondary-color);
|
| 772 |
+
}
|
| 773 |
+
|
| 774 |
+
.cm-cell {
|
| 775 |
+
font-weight: 600;
|
| 776 |
+
}
|
| 777 |
+
|
| 778 |
+
.cm-diag {
|
| 779 |
+
background: rgba(168, 85, 247, 0.15);
|
| 780 |
+
color: white;
|
| 781 |
+
}
|
| 782 |
+
|
| 783 |
+
.cm-zero {
|
| 784 |
+
opacity: 0.2;
|
| 785 |
+
}
|
| 786 |
+
|
| 787 |
+
/* Summary Mini Cards */
|
| 788 |
+
.summary-mini {
|
| 789 |
+
display: grid;
|
| 790 |
+
grid-template-columns: 1fr 1fr;
|
| 791 |
+
gap: 0.6rem;
|
| 792 |
+
}
|
| 793 |
+
|
| 794 |
+
.summary-mini-card {
|
| 795 |
+
background: rgba(255, 255, 255, 0.03);
|
| 796 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 797 |
+
padding: 0.6rem;
|
| 798 |
+
text-align: center;
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
.summary-mini-label {
|
| 802 |
+
display: block;
|
| 803 |
+
font-size: 7px;
|
| 804 |
+
letter-spacing: 0.1em;
|
| 805 |
+
text-transform: uppercase;
|
| 806 |
+
opacity: 0.6;
|
| 807 |
+
margin-bottom: 0.2rem;
|
| 808 |
+
}
|
| 809 |
+
|
| 810 |
+
.summary-mini-value {
|
| 811 |
+
display: block;
|
| 812 |
+
font-size: 1.2rem;
|
| 813 |
+
font-weight: 900;
|
| 814 |
+
color: var(--secondary-color);
|
| 815 |
+
}
|
| 816 |
+
|
| 817 |
+
/* Manifest Section (Collapsible) */
|
| 818 |
+
.manifest-section {
|
| 819 |
+
margin-top: 1rem;
|
| 820 |
+
background: rgba(255, 255, 255, 0.03);
|
| 821 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 822 |
+
padding: 0.8rem;
|
| 823 |
+
}
|
| 824 |
+
|
| 825 |
+
.manifest-header {
|
| 826 |
+
display: flex;
|
| 827 |
+
justify-content: space-between;
|
| 828 |
+
align-items: center;
|
| 829 |
+
cursor: pointer;
|
| 830 |
+
user-select: none;
|
| 831 |
+
}
|
| 832 |
+
|
| 833 |
+
.toggle-icon {
|
| 834 |
+
font-size: 10px;
|
| 835 |
+
opacity: 0.5;
|
| 836 |
+
transition: transform 0.3s;
|
| 837 |
+
}
|
| 838 |
+
|
| 839 |
+
.manifest-content {
|
| 840 |
+
transition: all 0.3s;
|
| 841 |
+
}
|
| 842 |
+
|
| 843 |
+
.stone-id-col {
|
| 844 |
+
font-weight: 700;
|
| 845 |
+
color: white;
|
| 846 |
+
font-size: 11px;
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
.result-col {
|
| 850 |
+
font-weight: 700;
|
| 851 |
+
color: var(--secondary-color);
|
| 852 |
+
font-size: 11px;
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
/* Sticky header row in manifest table */
|
| 856 |
+
.manifest-content table thead th {
|
| 857 |
+
position: sticky;
|
| 858 |
+
top: 0;
|
| 859 |
+
background: rgba(5, 5, 5, 0.98);
|
| 860 |
+
z-index: 10;
|
| 861 |
+
border-bottom: 2px solid rgba(168, 85, 247, 0.3);
|
| 862 |
}
|
| 863 |
|
| 864 |
.flush-section {
|
templates/index.html
CHANGED
|
@@ -132,21 +132,21 @@
|
|
| 132 |
const loaderText = document.querySelector('.loader-text');
|
| 133 |
let p = 0;
|
| 134 |
const itv = setInterval(() => {
|
| 135 |
-
if (p <
|
| 136 |
-
// Logic to simulate real work: slow down as it gets closer to
|
| 137 |
-
const increment = (
|
| 138 |
p += increment;
|
| 139 |
}
|
| 140 |
const displayP = Math.floor(p);
|
| 141 |
loaderBar.style.width = displayP + '%';
|
| 142 |
|
| 143 |
let phase = "ANALYZING";
|
| 144 |
-
if (displayP >
|
| 145 |
-
if (displayP >
|
| 146 |
-
if (displayP >
|
| 147 |
|
| 148 |
loaderText.innerText = `PROCS / ${phase} DATA [${displayP}%]`;
|
| 149 |
-
},
|
| 150 |
});
|
| 151 |
|
| 152 |
function confirmFlush() {
|
|
|
|
| 132 |
const loaderText = document.querySelector('.loader-text');
|
| 133 |
let p = 0;
|
| 134 |
const itv = setInterval(() => {
|
| 135 |
+
if (p < 92) {
|
| 136 |
+
// Logic to simulate real work: slow down as it gets closer to 92
|
| 137 |
+
const increment = (92 - p) * 0.03 + 0.1;
|
| 138 |
p += increment;
|
| 139 |
}
|
| 140 |
const displayP = Math.floor(p);
|
| 141 |
loaderBar.style.width = displayP + '%';
|
| 142 |
|
| 143 |
let phase = "ANALYZING";
|
| 144 |
+
if (displayP > 25) phase = "INFERENCING";
|
| 145 |
+
if (displayP > 60) phase = "GRADATING";
|
| 146 |
+
if (displayP > 85) phase = "COMPILING REPORT";
|
| 147 |
|
| 148 |
loaderText.innerText = `PROCS / ${phase} DATA [${displayP}%]`;
|
| 149 |
+
}, 100);
|
| 150 |
});
|
| 151 |
|
| 152 |
function confirmFlush() {
|
templates/report.html
CHANGED
|
@@ -13,176 +13,182 @@
|
|
| 13 |
<div class="atmospheric-grain"></div>
|
| 14 |
|
| 15 |
<div class="container animate-up">
|
| 16 |
-
<nav>
|
| 17 |
<div class="nav-logo">RESULTS / DASHBOARD</div>
|
| 18 |
<div class="nav-meta">
|
| 19 |
<span>STATUS: COMPLETE</span>
|
| 20 |
</div>
|
| 21 |
</nav>
|
| 22 |
|
| 23 |
-
<header style="display: flex; justify-content: space-between; align-items:
|
| 24 |
<div>
|
| 25 |
<p class="label-mini">Batch Processing Overview</p>
|
| 26 |
-
<h1 style="font-size:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
</div>
|
| 28 |
-
<a href="/download/{{ report_file }}" class="btn-launch"
|
| 29 |
-
style="text-decoration: none; padding: 0.8rem 1.5rem; font-size: 10px;">Export Result Dataset</a>
|
| 30 |
</header>
|
| 31 |
|
| 32 |
{% if metrics %}
|
| 33 |
-
<
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
<p class="metric-note">Balanced score (weighted).</p>
|
| 47 |
-
</div>
|
| 48 |
-
<div class="metric-item">
|
| 49 |
-
<span class="metric-value">{{ metrics.macro_f1|round(4) }}</span>
|
| 50 |
-
<span class="metric-label">MACRO F1</span>
|
| 51 |
-
<p class="metric-note">Unweighted average score.</p>
|
| 52 |
-
</div>
|
| 53 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
|
|
|
|
|
|
| 58 |
<thead>
|
| 59 |
<tr>
|
| 60 |
-
<th>
|
| 61 |
-
<th>
|
| 62 |
-
<th>
|
| 63 |
<th>F1</th>
|
| 64 |
-
<th>
|
| 65 |
</tr>
|
| 66 |
</thead>
|
| 67 |
<tbody>
|
| 68 |
{% for item in metrics.class_metrics %}
|
| 69 |
<tr>
|
| 70 |
-
<td
|
| 71 |
<td>{{ item.precision }}</td>
|
| 72 |
<td>{{ item.recall }}</td>
|
| 73 |
<td>{{ item.f1 }}</td>
|
| 74 |
-
<td
|
| 75 |
</tr>
|
| 76 |
{% endfor %}
|
| 77 |
</tbody>
|
| 78 |
</table>
|
| 79 |
</div>
|
| 80 |
-
</
|
| 81 |
</div>
|
| 82 |
|
| 83 |
-
<
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
{% endfor %}
|
| 94 |
-
</tr>
|
| 95 |
-
</thead>
|
| 96 |
-
<tbody>
|
| 97 |
-
{% for row_idx in range(metrics.confusion_matrix.matrix|length) %}
|
| 98 |
-
<tr>
|
| 99 |
-
<td
|
| 100 |
-
style="background: rgba(255,255,255,0.03); font-weight: 700; color: var(--secondary-color); padding: 0.5rem;">
|
| 101 |
-
{{ metrics.confusion_matrix.labels[row_idx] }}
|
| 102 |
-
</td>
|
| 103 |
-
{% for col_idx in range(metrics.confusion_matrix.matrix[row_idx]|length) %}
|
| 104 |
-
{% set val = metrics.confusion_matrix.matrix[row_idx][col_idx] %}
|
| 105 |
-
<td class="cm-value {% if row_idx == col_idx %}cm-match{% endif %} {% if val == 0 %}cm-dimmed{% endif %}"
|
| 106 |
-
style="padding: 0.5rem;">
|
| 107 |
-
{{ val }}
|
| 108 |
-
</td>
|
| 109 |
-
{% endfor %}
|
| 110 |
-
</tr>
|
| 111 |
{% endfor %}
|
| 112 |
-
</
|
| 113 |
-
</
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
</div>
|
| 126 |
</div>
|
| 127 |
-
</
|
| 128 |
</div>
|
| 129 |
</div>
|
| 130 |
{% endif %}
|
| 131 |
|
| 132 |
-
<
|
| 133 |
-
|
| 134 |
-
<div class="
|
| 135 |
-
<
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
<
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
<
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
{%
|
| 156 |
-
<
|
| 157 |
-
row.
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
{%
|
| 161 |
-
<
|
| 162 |
-
{%
|
| 163 |
-
<
|
| 164 |
-
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
{% endfor %}
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
alt="Diamond Preview">
|
| 171 |
-
{% else %}
|
| 172 |
-
<span style="font-size: 10px; opacity: 0.3;">NO IMAGE</span>
|
| 173 |
-
{% endif %}
|
| 174 |
-
</td>
|
| 175 |
-
</tr>
|
| 176 |
-
{% endfor %}
|
| 177 |
-
</tbody>
|
| 178 |
-
</table>
|
| 179 |
</div>
|
| 180 |
</section>
|
| 181 |
|
| 182 |
-
<footer style="margin-top:
|
| 183 |
<a href="/"
|
| 184 |
-
style="font-size:
|
| 185 |
-
|
| 186 |
</footer>
|
| 187 |
</div>
|
| 188 |
|
|
@@ -194,6 +200,18 @@
|
|
| 194 |
|
| 195 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
| 196 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
function openModal(src) {
|
| 198 |
const modal = document.getElementById('image-modal');
|
| 199 |
const modalImg = document.getElementById('modal-img');
|
|
@@ -206,12 +224,11 @@
|
|
| 206 |
modal.classList.remove('active');
|
| 207 |
}
|
| 208 |
|
| 209 |
-
// Close modal on background click
|
| 210 |
document.getElementById('image-modal').onclick = function (e) {
|
| 211 |
if (e.target === this) closeModal();
|
| 212 |
};
|
| 213 |
|
| 214 |
-
//
|
| 215 |
const vertexShader = `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`;
|
| 216 |
const fragmentShader = `
|
| 217 |
uniform float uTime;
|
|
|
|
| 13 |
<div class="atmospheric-grain"></div>
|
| 14 |
|
| 15 |
<div class="container animate-up">
|
| 16 |
+
<nav style="padding: 0.5rem 0;">
|
| 17 |
<div class="nav-logo">RESULTS / DASHBOARD</div>
|
| 18 |
<div class="nav-meta">
|
| 19 |
<span>STATUS: COMPLETE</span>
|
| 20 |
</div>
|
| 21 |
</nav>
|
| 22 |
|
| 23 |
+
<header style="display: flex; justify-content: space-between; align-items: center; margin: 0.5rem 0;">
|
| 24 |
<div>
|
| 25 |
<p class="label-mini">Batch Processing Overview</p>
|
| 26 |
+
<h1 style="font-size: 2.5rem; line-height: 1;">ANALYTICS <span class="italic">REPORT</span></h1>
|
| 27 |
+
</div>
|
| 28 |
+
<div style="display: flex; gap: 0.8rem;">
|
| 29 |
+
<a href="/" class="btn-launch"
|
| 30 |
+
style="text-decoration: none; padding: 0.6rem 1.2rem; font-size: 9px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.5); color: white; opacity: 1;">←
|
| 31 |
+
NEW BATCH</a>
|
| 32 |
+
<a href="/download/{{ report_file }}" class="btn-launch"
|
| 33 |
+
style="text-decoration: none; padding: 0.6rem 1.2rem; font-size: 9px;">EXPORT DATASET</a>
|
| 34 |
</div>
|
|
|
|
|
|
|
| 35 |
</header>
|
| 36 |
|
| 37 |
{% if metrics %}
|
| 38 |
+
<!-- Single-Screen Dashboard Grid -->
|
| 39 |
+
<div class="dashboard-grid">
|
| 40 |
+
<!-- Left Column: KPIs + Class Decomposition -->
|
| 41 |
+
<div class="dashboard-main">
|
| 42 |
+
<!-- KPI Row -->
|
| 43 |
+
<div class="kpi-row">
|
| 44 |
+
<div class="kpi-card">
|
| 45 |
+
<span class="kpi-value">{{ (metrics.accuracy * 100)|round(1) }}%</span>
|
| 46 |
+
<span class="kpi-label">ACCURACY</span>
|
| 47 |
+
</div>
|
| 48 |
+
<div class="kpi-card">
|
| 49 |
+
<span class="kpi-value">{{ metrics.f1|round(3) }}</span>
|
| 50 |
+
<span class="kpi-label">WEIGHTED F1</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
</div>
|
| 52 |
+
<div class="kpi-card">
|
| 53 |
+
<span class="kpi-value">{{ metrics.macro_f1|round(3) }}</span>
|
| 54 |
+
<span class="kpi-label">MACRO F1</span>
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
|
| 58 |
+
<!-- Class-Wise Table (Compact) -->
|
| 59 |
+
<div class="compact-table-section">
|
| 60 |
+
<p class="section-label">CLASS-WISE METRICS</p>
|
| 61 |
+
<div class="compact-table-wrapper">
|
| 62 |
+
<table class="compact-table">
|
| 63 |
<thead>
|
| 64 |
<tr>
|
| 65 |
+
<th>GRADE</th>
|
| 66 |
+
<th>PREC</th>
|
| 67 |
+
<th>RECALL</th>
|
| 68 |
<th>F1</th>
|
| 69 |
+
<th>SUP</th>
|
| 70 |
</tr>
|
| 71 |
</thead>
|
| 72 |
<tbody>
|
| 73 |
{% for item in metrics.class_metrics %}
|
| 74 |
<tr>
|
| 75 |
+
<td class="grade-col">{{ item.label }}</td>
|
| 76 |
<td>{{ item.precision }}</td>
|
| 77 |
<td>{{ item.recall }}</td>
|
| 78 |
<td>{{ item.f1 }}</td>
|
| 79 |
+
<td class="sup-col">{{ item.support }}</td>
|
| 80 |
</tr>
|
| 81 |
{% endfor %}
|
| 82 |
</tbody>
|
| 83 |
</table>
|
| 84 |
</div>
|
| 85 |
+
</div>
|
| 86 |
</div>
|
| 87 |
|
| 88 |
+
<!-- Right Column: Confusion Matrix -->
|
| 89 |
+
<div class="dashboard-sidebar">
|
| 90 |
+
<p class="section-label">CONFUSION MATRIX</p>
|
| 91 |
+
<div class="cm-compact">
|
| 92 |
+
<table class="cm-compact-table">
|
| 93 |
+
<thead>
|
| 94 |
+
<tr>
|
| 95 |
+
<th class="cm-corner">A\P</th>
|
| 96 |
+
{% for label in metrics.confusion_matrix.labels %}
|
| 97 |
+
<th>{{ label }}</th>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
{% endfor %}
|
| 99 |
+
</tr>
|
| 100 |
+
</thead>
|
| 101 |
+
<tbody>
|
| 102 |
+
{% for row_idx in range(metrics.confusion_matrix.matrix|length) %}
|
| 103 |
+
<tr>
|
| 104 |
+
<td class="cm-row-label">{{ metrics.confusion_matrix.labels[row_idx] }}</td>
|
| 105 |
+
{% for col_idx in range(metrics.confusion_matrix.matrix[row_idx]|length) %}
|
| 106 |
+
{% set val = metrics.confusion_matrix.matrix[row_idx][col_idx] %}
|
| 107 |
+
<td
|
| 108 |
+
class="cm-cell {% if row_idx == col_idx %}cm-diag{% endif %} {% if val == 0 %}cm-zero{% endif %}">
|
| 109 |
+
{{ val }}
|
| 110 |
+
</td>
|
| 111 |
+
{% endfor %}
|
| 112 |
+
</tr>
|
| 113 |
+
{% endfor %}
|
| 114 |
+
</tbody>
|
| 115 |
+
</table>
|
| 116 |
+
</div>
|
| 117 |
|
| 118 |
+
<!-- Summary Cards -->
|
| 119 |
+
<div class="summary-mini">
|
| 120 |
+
<div class="summary-mini-card">
|
| 121 |
+
<span class="summary-mini-label">MACRO F1</span>
|
| 122 |
+
<span class="summary-mini-value">{{ metrics.macro_f1|round(3) }}</span>
|
| 123 |
+
</div>
|
| 124 |
+
<div class="summary-mini-card">
|
| 125 |
+
<span class="summary-mini-label">ACCURACY</span>
|
| 126 |
+
<span class="summary-mini-value">{{ (metrics.accuracy * 100)|round(1) }}%</span>
|
|
|
|
| 127 |
</div>
|
| 128 |
+
</div>
|
| 129 |
</div>
|
| 130 |
</div>
|
| 131 |
{% endif %}
|
| 132 |
|
| 133 |
+
<!-- Inference Manifest (Expanded by default for immediate access) -->
|
| 134 |
+
<section class="manifest-section">
|
| 135 |
+
<div class="manifest-header" onclick="toggleManifest()">
|
| 136 |
+
<p class="section-label" style="margin: 0;">INFERENCE MANIFEST</p>
|
| 137 |
+
<span class="toggle-icon" id="manifest-toggle">▼</span>
|
| 138 |
+
</div>
|
| 139 |
+
<div class="manifest-content" id="manifest-content" style="display: block;">
|
| 140 |
+
<div class="table-wrapper" style="max-height: 350px; margin-top: 0.5rem;">
|
| 141 |
+
<table>
|
| 142 |
+
<thead>
|
| 143 |
+
<tr>
|
| 144 |
+
<th>STONE ID</th>
|
| 145 |
+
{% for feature in model_features %}
|
| 146 |
+
<th>{{ feature }}</th>
|
| 147 |
+
{% endfor %}
|
| 148 |
+
<th>AI RESULT</th>
|
| 149 |
+
{% for col in out_of_box_cols %}
|
| 150 |
+
<th>{{ col }}</th>
|
| 151 |
+
{% endfor %}
|
| 152 |
+
<th>IMG</th>
|
| 153 |
+
</tr>
|
| 154 |
+
</thead>
|
| 155 |
+
<tbody>
|
| 156 |
+
{% for row in report_data %}
|
| 157 |
+
<tr>
|
| 158 |
+
<td class="stone-id-col">{{ row.L_Code }}</td>
|
| 159 |
+
{% for feature in model_features %}
|
| 160 |
+
<td style="opacity: 0.7; font-size: 11px;">{{ row[feature] }}</td>
|
| 161 |
+
{% endfor %}
|
| 162 |
+
<td class="result-col">{{ row.Predicted_FGrdCol }}</td>
|
| 163 |
+
{% for col in out_of_box_cols %}
|
| 164 |
+
<td style="font-size: 10px;">
|
| 165 |
+
{% if row[col] and row[col]|string != 'nan' %}
|
| 166 |
+
<span class="badge-oob">{{ row[col] }}</span>
|
| 167 |
+
{% else %}
|
| 168 |
+
<span style="opacity: 0.2;">-</span>
|
| 169 |
+
{% endif %}
|
| 170 |
+
</td>
|
| 171 |
+
{% endfor %}
|
| 172 |
+
<td>
|
| 173 |
+
{% if row.Image_Path != 'N/A' %}
|
| 174 |
+
<img src="/image/{{ row.Image_Path }}" class="img-thumb"
|
| 175 |
+
onclick="openModal(this.src)" alt="Diamond">
|
| 176 |
+
{% else %}
|
| 177 |
+
<span style="font-size: 8px; opacity: 0.3;">N/A</span>
|
| 178 |
+
{% endif %}
|
| 179 |
+
</td>
|
| 180 |
+
</tr>
|
| 181 |
{% endfor %}
|
| 182 |
+
</tbody>
|
| 183 |
+
</table>
|
| 184 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
</div>
|
| 186 |
</section>
|
| 187 |
|
| 188 |
+
<footer style="margin-top: 1rem; padding-bottom: 1rem; text-align: center;">
|
| 189 |
<a href="/"
|
| 190 |
+
style="font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: white; opacity: 0.5; text-decoration: none;">←
|
| 191 |
+
NEW BATCH</a>
|
| 192 |
</footer>
|
| 193 |
</div>
|
| 194 |
|
|
|
|
| 200 |
|
| 201 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
| 202 |
<script>
|
| 203 |
+
function toggleManifest() {
|
| 204 |
+
const content = document.getElementById('manifest-content');
|
| 205 |
+
const toggle = document.getElementById('manifest-toggle');
|
| 206 |
+
if (content.style.display === 'none') {
|
| 207 |
+
content.style.display = 'block';
|
| 208 |
+
toggle.textContent = '▼';
|
| 209 |
+
} else {
|
| 210 |
+
content.style.display = 'none';
|
| 211 |
+
toggle.textContent = '▶';
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
function openModal(src) {
|
| 216 |
const modal = document.getElementById('image-modal');
|
| 217 |
const modalImg = document.getElementById('modal-img');
|
|
|
|
| 224 |
modal.classList.remove('active');
|
| 225 |
}
|
| 226 |
|
|
|
|
| 227 |
document.getElementById('image-modal').onclick = function (e) {
|
| 228 |
if (e.target === this) closeModal();
|
| 229 |
};
|
| 230 |
|
| 231 |
+
// Background shader
|
| 232 |
const vertexShader = `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`;
|
| 233 |
const fragmentShader = `
|
| 234 |
uniform float uTime;
|