Sirius16 commited on
Commit
79aeec8
·
verified ·
1 Parent(s): 6f3c25d

Upload 25 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/BrainAI.png filter=lfs diff=lfs merge=lfs -text
37
+ static/DSC[[:space:]]UI.png filter=lfs diff=lfs merge=lfs -text
38
+ static/foto[[:space:]]sampel/Hilmy.jpg filter=lfs diff=lfs merge=lfs -text
39
+ static/foto[[:space:]]sampel/Icha.png filter=lfs diff=lfs merge=lfs -text
40
+ static/foto[[:space:]]sampel/Jason.jpg filter=lfs diff=lfs merge=lfs -text
41
+ static/foto[[:space:]]sampel/Lil[[:space:]]Bah[[:space:]]Lil.jpg filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /code
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
8
+
9
+ RUN useradd -m -u 1000 user
10
+ USER user
11
+ ENV HOME=/home/user \
12
+ PATH=/home/user/.local/bin:$PATH
13
+
14
+ WORKDIR $HOME/app
15
+
16
+ COPY --chown=user . $HOME/app
17
+
18
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import torch
4
+ import torch.nn.functional as F
5
+ from flask import Flask, render_template, request, jsonify
6
+ from torchvision import transforms
7
+ from PIL import Image
8
+
9
+ from resnet_mlp import ResNetMLP
10
+ from resnet_kan import ResNetKAN
11
+
12
+ app = Flask(__name__)
13
+
14
+ CLASSES = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
15
+ IMAGENET_MEAN = [0.485, 0.456, 0.406]
16
+ IMAGENET_STD = [0.229, 0.224, 0.225]
17
+
18
+ inference_transform = transforms.Compose([
19
+ transforms.Resize(256),
20
+ transforms.ToTensor(),
21
+ transforms.Normalize(IMAGENET_MEAN, IMAGENET_STD),
22
+ ])
23
+
24
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
25
+
26
+ def load_model_weights(model, path):
27
+ if os.path.exists(path):
28
+ model.load_state_dict(torch.load(path, map_location=device))
29
+ model.eval()
30
+ return model.to(device)
31
+
32
+ FREEZE_BACKBONE = True
33
+
34
+ model_mlp = ResNetMLP(
35
+ num_classes=10,
36
+ freeze_backbone=FREEZE_BACKBONE,
37
+ hidden_dim=512
38
+ )
39
+ model_mlp = load_model_weights(model_mlp, "weights/tesresnet_mlp_cifar10_run1.pth")
40
+
41
+ model_kan = ResNetKAN(
42
+ num_classes=10,
43
+ freeze_backbone=FREEZE_BACKBONE
44
+ )
45
+ model_kan = load_model_weights(model_kan, "weights/resnet_kan_cifar10_run1.pth")
46
+
47
+ @app.route('/')
48
+ def home():
49
+ return render_template('resnet.html')
50
+
51
+ @app.route('/api/predict', methods=['POST'])
52
+ def predict():
53
+ if 'file' not in request.files:
54
+ return jsonify({"status": "error", "message": "No file"}), 400
55
+
56
+ file = request.files['file']
57
+ img_bytes = file.read()
58
+
59
+ try:
60
+ image = Image.open(io.BytesIO(img_bytes)).convert('RGB')
61
+ tensor_img = inference_transform(image).unsqueeze(0).to(device)
62
+
63
+ with torch.no_grad():
64
+ out_1 = model_mlp(tensor_img)
65
+ prob_1 = F.softmax(out_1, dim=1)
66
+ conf_1, idx_1 = torch.max(prob_1, 1)
67
+
68
+ out_2 = model_kan(tensor_img)
69
+ prob_2 = F.softmax(out_2, dim=1)
70
+ conf_2, idx_2 = torch.max(prob_2, 1)
71
+
72
+ p1_list = prob_1[0].tolist()
73
+ p2_list = prob_2[0].tolist()
74
+
75
+ all_p1 = [{"class": CLASSES[i], "confidence": round(p1_list[i] * 100, 2)} for i in range(10)]
76
+ all_p2 = [{"class": CLASSES[i], "confidence": round(p2_list[i] * 100, 2)} for i in range(10)]
77
+
78
+ all_p1.sort(key=lambda x: x['confidence'], reverse=True)
79
+ all_p2.sort(key=lambda x: x['confidence'], reverse=True)
80
+
81
+ is_outlier = False
82
+ if conf_1.item() < 0.35 and conf_2.item() < 0.35:
83
+ is_outlier = True
84
+
85
+ return jsonify({
86
+ "status": "success",
87
+ "is_outlier": is_outlier,
88
+ "model_1": {
89
+ "class": CLASSES[idx_1.item()],
90
+ "confidence": round(conf_1.item() * 100, 2),
91
+ "all_probs": all_p1
92
+ },
93
+ "model_2": {
94
+ "class": CLASSES[idx_2.item()],
95
+ "confidence": round(conf_2.item() * 100, 2),
96
+ "all_probs": all_p2
97
+ }
98
+ })
99
+ except Exception as e:
100
+ return jsonify({"status": "error", "message": str(e)})
101
+
102
+ if __name__ == '__main__':
103
+ app.run(host='0.0.0.0', port=7860)
kan1.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import math
5
+
6
+ class KANLinear(nn.Module):
7
+ def __init__(
8
+ self,
9
+ in_features,
10
+ out_features,
11
+ grid_size=5,
12
+ spline_order=3,
13
+ scale_noise=0.1,
14
+ scale_base= 1.0,
15
+ scale_spline=1.0,
16
+ enable_standalone_scale_spline=True,
17
+ base_activation=nn.SiLU,
18
+ grid_eps=0.02,
19
+ grid_range=[-1, 1],
20
+ ):
21
+ super(KANLinear, self).__init__()
22
+ self.in_features = in_features
23
+ self.out_features = out_features
24
+ self.grid_size = grid_size
25
+ self.spline_order = spline_order
26
+
27
+ h = (grid_range[1] - grid_range[0]) / grid_size
28
+ grid = (
29
+ (
30
+ torch.arange(-spline_order, grid_size + spline_order + 1) * h
31
+ + grid_range[0]
32
+ )
33
+ .expand(in_features, -1)
34
+ .contiguous()
35
+ )
36
+ self.register_buffer("grid", grid)
37
+
38
+ self.base_weight = nn.Parameter(torch.Tensor(out_features, in_features))
39
+ self.spline_weight = nn.Parameter(
40
+ torch.Tensor(out_features, in_features, grid_size + spline_order)
41
+ )
42
+ if enable_standalone_scale_spline:
43
+ self.spline_scaler = nn.Parameter(
44
+ torch.Tensor(out_features, in_features)
45
+ )
46
+
47
+ self.scale_noise = scale_noise
48
+ self.scale_base = scale_base
49
+ self.scale_spline = scale_spline
50
+ self.enable_standalone_scale_spline = enable_standalone_scale_spline
51
+ self.base_activation = base_activation()
52
+ self.grid_eps = grid_eps
53
+ self.reset_parameters()
54
+
55
+ def reset_parameters(self):
56
+ nn.init.kaiming_uniform_(self.base_weight, a=math.sqrt(5) * self.scale_base)
57
+ with torch.no_grad():
58
+ noise = (
59
+ (
60
+ torch.rand(self.grid_size + 1, self.in_features, self.out_features)
61
+ - 1 / 2
62
+ )
63
+ * self.scale_noise
64
+ / self.grid_size
65
+ )
66
+ self.spline_weight.data.copy_(
67
+ (self.scale_spline if not self.enable_standalone_scale_spline else 1.0)
68
+ * self.curve2coeff(
69
+ self.grid.T[self.spline_order : -self.spline_order],
70
+ noise,
71
+ )
72
+ )
73
+ if self.enable_standalone_scale_spline:
74
+ nn.init.kaiming_uniform_(self.spline_scaler, a=math.sqrt(5) * self.scale_spline)
75
+
76
+ def b_splines(self, x):
77
+ assert x.dim() == 2 and x.size(1) == self.in_features
78
+ grid = self.grid
79
+ x = x.unsqueeze(-1)
80
+ bases = ((x >= grid[:, :-1]) & (x < grid[:, 1:])).to(x.dtype)
81
+ for k in range(1, self.spline_order + 1):
82
+ bases = (
83
+ (x - grid[:, : -(k + 1)])
84
+ / (grid[:, k:-1] - grid[:, : -(k + 1)])
85
+ * bases[:, :, :-1]
86
+ ) + (
87
+ (grid[:, k + 1 :] - x)
88
+ / (grid[:, k + 1 :] - grid[:, 1:(-k)])
89
+ * bases[:, :, 1:]
90
+ )
91
+ return bases.contiguous()
92
+
93
+ def curve2coeff(self, x, y):
94
+ A = self.b_splines(x).transpose(0, 1)
95
+ B = y.transpose(0, 1)
96
+ solution = torch.linalg.lstsq(A, B).solution
97
+ result = solution.permute(2, 0, 1)
98
+ return result.contiguous()
99
+
100
+ @property
101
+ def scaled_spline_weight(self):
102
+ return self.spline_weight * (
103
+ self.spline_scaler.unsqueeze(-1)
104
+ if self.enable_standalone_scale_spline
105
+ else 1.0
106
+ )
107
+
108
+ def forward(self, x):
109
+ if x.dim() != 2 or x.size(1) != self.in_features:
110
+ x = x.view(x.size(0), -1)
111
+ base_output = F.linear(self.base_activation(x), self.base_weight)
112
+ spline_output = F.linear(
113
+ self.b_splines(x).view(x.size(0), -1),
114
+ self.scaled_spline_weight.view(self.out_features, -1),
115
+ )
116
+ return base_output + spline_output
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Flask
2
+ torch
3
+ torchvision
4
+ Pillow
resnet_kan.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ from torchvision.models import resnet50, ResNet50_Weights
3
+ from kan1 import KANLinear
4
+
5
+ class ResNetKAN(nn.Module):
6
+ def __init__(self, num_classes=10, freeze_backbone=True):
7
+ super().__init__()
8
+ weights = ResNet50_Weights.DEFAULT
9
+ self.resnet = resnet50(weights=weights)
10
+ if freeze_backbone:
11
+ for p in self.resnet.parameters():
12
+ p.requires_grad = False
13
+ for p in self.resnet.layer3.parameters():
14
+ p.requires_grad = True
15
+ for p in self.resnet.layer4.parameters():
16
+ p.requires_grad = True
17
+ num_features = self.resnet.fc.in_features
18
+ self.resnet.fc = nn.Identity()
19
+ self.kan1 = KANLinear(num_features, 512)
20
+ self.bn1 = nn.BatchNorm1d(512)
21
+ self.act1 = nn.ReLU()
22
+ self.kan2 = KANLinear(512, 512)
23
+ self.bn2 = nn.BatchNorm1d(512)
24
+ self.act2 = nn.ReLU()
25
+ self.kan3 = KANLinear(512, num_classes)
26
+
27
+ def forward(self, x):
28
+ x = self.resnet(x)
29
+ x = x.view(x.size(0), -1)
30
+ x = self.kan1(x)
31
+ x = self.bn1(x)
32
+ x = self.act1(x)
33
+ x = self.kan2(x)
34
+ x = self.bn2(x)
35
+ x = self.act2(x)
36
+ x = self.kan3(x)
37
+ return x
resnet_mlp.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ from torchvision.models import resnet50, ResNet50_Weights
3
+
4
+ class MLPHead(nn.Module):
5
+ def __init__(self, in_features, hidden_dim, num_classes):
6
+ super().__init__()
7
+ self.net = nn.Sequential(
8
+ nn.Linear(in_features, hidden_dim),
9
+ nn.BatchNorm1d(hidden_dim),
10
+ nn.ReLU(inplace=True),
11
+ nn.Linear(hidden_dim, hidden_dim),
12
+ nn.BatchNorm1d(hidden_dim),
13
+ nn.ReLU(inplace=True),
14
+ nn.Linear(hidden_dim, num_classes),
15
+ )
16
+
17
+ def forward(self, x):
18
+ return self.net(x)
19
+
20
+ class ResNetMLP(nn.Module):
21
+ def __init__(self, num_classes=10, freeze_backbone=True, hidden_dim=512):
22
+ super().__init__()
23
+ weights = ResNet50_Weights.DEFAULT
24
+ self.resnet = resnet50(weights=weights)
25
+ if freeze_backbone:
26
+ for p in self.resnet.parameters():
27
+ p.requires_grad = False
28
+ for p in self.resnet.layer3.parameters():
29
+ p.requires_grad = True
30
+ for p in self.resnet.layer4.parameters():
31
+ p.requires_grad = True
32
+ num_features = self.resnet.fc.in_features
33
+ self.resnet.fc = nn.Identity()
34
+ self.mlp_head = MLPHead(
35
+ in_features=num_features,
36
+ hidden_dim=hidden_dim,
37
+ num_classes=num_classes,
38
+ )
39
+
40
+ def forward(self, x):
41
+ x = self.resnet(x)
42
+ x = x.view(x.size(0), -1)
43
+ x = self.mlp_head(x)
44
+ return x
static/BrainAI.png ADDED

Git LFS Details

  • SHA256: 0a76af116739256f0c3f403b292ec14e27bb7ae6cad62d3795659f9263597084
  • Pointer size: 132 Bytes
  • Size of remote file: 1.87 MB
static/DSC UI.png ADDED

Git LFS Details

  • SHA256: d0655fa1573a1cf2cdd18ada05739b4435783a1fdc9fc14071ce075b6cfee06b
  • Pointer size: 131 Bytes
  • Size of remote file: 183 kB
static/foto sampel/Ahdi.jpg ADDED
static/foto sampel/Autistic.77.jpg ADDED
static/foto sampel/Autistic.8.jpg ADDED
static/foto sampel/Hilmy.jpg ADDED

Git LFS Details

  • SHA256: ce309d0a135ed792dcde8b04f58b7f45cb7bc0da73fa5f8d0af5e0158f127109
  • Pointer size: 131 Bytes
  • Size of remote file: 161 kB
static/foto sampel/Icha.png ADDED

Git LFS Details

  • SHA256: bf97d7a141411d091f1bd1cbd947486d0575ac78066b6d9f4ae4e6a1a6ff6a64
  • Pointer size: 131 Bytes
  • Size of remote file: 867 kB
static/foto sampel/Jason.jpg ADDED

Git LFS Details

  • SHA256: 68b88f412c5672bd5bf37a0031630df6617b25797c995af658f35bf51495e222
  • Pointer size: 131 Bytes
  • Size of remote file: 110 kB
static/foto sampel/Jokowi.jpg ADDED
static/foto sampel/Koh Owi.jpg ADDED
static/foto sampel/Lil Bah Lil.jpg ADDED

Git LFS Details

  • SHA256: 03457ac9a6748f65e4c3ef56ce1d8ab9d36c701ed4df08252a40f88a53308d81
  • Pointer size: 131 Bytes
  • Size of remote file: 989 kB
static/foto sampel/Non_Autistic.29.jpg ADDED
static/foto sampel/Non_Autistic.8.jpg ADDED
static/foto sampel/Prabs.jpg ADDED
templates/resnet.html ADDED
@@ -0,0 +1,850 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ResNet CIFAR10 - Research Deployment</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
9
+
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ font-family: 'Poppins', sans-serif;
15
+ }
16
+
17
+ body {
18
+ background-color: #f8fafc;
19
+ color: #334155;
20
+ }
21
+
22
+ .hero-section {
23
+ background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
24
+ color: white;
25
+ padding: 70px 8%;
26
+ text-align: center;
27
+ }
28
+
29
+ .hero-title {
30
+ font-size: 32px;
31
+ font-weight: 700;
32
+ margin-bottom: 12px;
33
+ line-height: 1.4;
34
+ }
35
+
36
+ .hero-subtitle {
37
+ font-size: 18px;
38
+ color: #cbd5e0;
39
+ font-weight: 500;
40
+ letter-spacing: 0.5px;
41
+ }
42
+
43
+ .support-section {
44
+ background: white;
45
+ padding: 40px 8%;
46
+ text-align: center;
47
+ border-bottom: 1px solid #e2e8f0;
48
+ }
49
+
50
+ .support-title {
51
+ font-size: 14px;
52
+ font-weight: 700;
53
+ color: #64748b;
54
+ letter-spacing: 2px;
55
+ text-transform: uppercase;
56
+ margin-bottom: 30px;
57
+ }
58
+
59
+ .logos-container {
60
+ display: flex;
61
+ justify-content: center;
62
+ align-items: center;
63
+ gap: 60px;
64
+ flex-wrap: wrap;
65
+ }
66
+
67
+ .logo-item {
68
+ display: flex;
69
+ flex-direction: column;
70
+ align-items: center;
71
+ gap: 15px;
72
+ }
73
+
74
+ .logo-img {
75
+ height: 80px;
76
+ }
77
+
78
+ .logo-text {
79
+ font-size: 12px;
80
+ font-weight: 600;
81
+ color: #475569;
82
+ max-width: 250px;
83
+ }
84
+
85
+ .about-section {
86
+ padding: 60px 8%;
87
+ background: #f1f5f9;
88
+ display: flex;
89
+ flex-direction: column;
90
+ align-items: center;
91
+ }
92
+
93
+ .section-header {
94
+ text-align: center;
95
+ margin-bottom: 40px;
96
+ }
97
+
98
+ .section-header h2 {
99
+ font-size: 28px;
100
+ color: #1e293b;
101
+ margin-bottom: 10px;
102
+ font-weight: 700;
103
+ }
104
+
105
+ .section-header p {
106
+ color: #64748b;
107
+ font-size: 15px;
108
+ }
109
+
110
+ .class-grid {
111
+ display: flex;
112
+ flex-wrap: wrap;
113
+ gap: 15px;
114
+ justify-content: center;
115
+ max-width: 1000px;
116
+ }
117
+
118
+ .class-card {
119
+ background: white;
120
+ padding: 20px 15px;
121
+ border-radius: 16px;
122
+ text-align: center;
123
+ box-shadow: 0 4px 6px rgba(0,0,0,0.02);
124
+ border: 1px solid #e2e8f0;
125
+ width: 140px;
126
+ cursor: pointer;
127
+ transition: transform 0.2s, border-color 0.2s;
128
+ display: flex;
129
+ flex-direction: column;
130
+ align-items: center;
131
+ gap: 15px;
132
+ }
133
+
134
+ .class-card:hover {
135
+ transform: translateY(-5px);
136
+ border-color: #3b82f6;
137
+ box-shadow: 0 10px 15px rgba(59, 130, 246, 0.1);
138
+ }
139
+
140
+ .class-emoji {
141
+ font-size: 40px;
142
+ line-height: 1;
143
+ }
144
+
145
+ .class-card span {
146
+ display: block;
147
+ font-weight: 700;
148
+ color: #334155;
149
+ font-size: 15px;
150
+ }
151
+
152
+ .detection-section {
153
+ padding: 60px 8%;
154
+ display: flex;
155
+ gap: 30px;
156
+ align-items: stretch;
157
+ }
158
+
159
+ .panel {
160
+ background: white;
161
+ border-radius: 20px;
162
+ padding: 30px;
163
+ border: 1px solid #e2e8f0;
164
+ box-shadow: 0 10px 25px rgba(0,0,0,0.02);
165
+ flex: 1;
166
+ display: flex;
167
+ flex-direction: column;
168
+ }
169
+
170
+ .panel-header {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: 12px;
174
+ margin-bottom: 25px;
175
+ color: #1a202c;
176
+ font-size: 1.1rem;
177
+ font-weight: 600;
178
+ }
179
+
180
+ .panel-svg {
181
+ color: #3b82f6;
182
+ display: flex;
183
+ }
184
+
185
+ .upload-area {
186
+ border: 2px dashed #cbd5e0;
187
+ border-radius: 15px;
188
+ height: 400px;
189
+ display: flex;
190
+ flex-direction: column;
191
+ align-items: center;
192
+ justify-content: center;
193
+ cursor: pointer;
194
+ background: #ffffff;
195
+ transition: 0.3s;
196
+ position: relative;
197
+ }
198
+
199
+ .upload-area:hover {
200
+ border-color: #3b82f6;
201
+ background: #f8fafc;
202
+ }
203
+
204
+ .plus-icon {
205
+ font-size: 50px;
206
+ color: #94a3b8;
207
+ font-weight: 300;
208
+ line-height: 1;
209
+ margin-bottom: 15px;
210
+ }
211
+
212
+ .upload-text-main {
213
+ font-weight: 700;
214
+ color: #334155;
215
+ font-size: 16px;
216
+ margin-bottom: 5px;
217
+ }
218
+
219
+ .upload-text-sub {
220
+ color: #94a3b8;
221
+ font-size: 12px;
222
+ }
223
+
224
+ #image-preview-container {
225
+ display: none;
226
+ flex-direction: column;
227
+ align-items: center;
228
+ justify-content: center;
229
+ width: 100%;
230
+ height: 100%;
231
+ padding: 20px;
232
+ }
233
+
234
+ #image-preview {
235
+ max-width: 100%;
236
+ max-height: 280px;
237
+ border-radius: 10px;
238
+ margin-bottom: 20px;
239
+ box-shadow: 0 4px 10px rgba(0,0,0,0.1);
240
+ }
241
+
242
+ .btn-change {
243
+ background: white;
244
+ border: 1px solid #cbd5e0;
245
+ padding: 8px 20px;
246
+ border-radius: 8px;
247
+ font-weight: 500;
248
+ color: #475569;
249
+ cursor: pointer;
250
+ }
251
+
252
+ .btn-change:hover {
253
+ border-color: #3b82f6;
254
+ color: #3b82f6;
255
+ }
256
+
257
+ .result-panel {
258
+ flex: 1.2;
259
+ }
260
+
261
+ .empty-state {
262
+ display: flex;
263
+ flex-direction: column;
264
+ align-items: center;
265
+ justify-content: center;
266
+ height: 100%;
267
+ text-align: center;
268
+ }
269
+
270
+ .empty-icon {
271
+ width: 50px;
272
+ height: 50px;
273
+ stroke: #cbd5e0;
274
+ stroke-width: 1.5;
275
+ fill: none;
276
+ margin-bottom: 15px;
277
+ }
278
+
279
+ .empty-title {
280
+ font-weight: 700;
281
+ color: #334155;
282
+ margin-bottom: 8px;
283
+ }
284
+
285
+ .empty-desc {
286
+ color: #94a3b8;
287
+ font-size: 13px;
288
+ max-width: 250px;
289
+ line-height: 1.6;
290
+ }
291
+
292
+ .cards-row {
293
+ display: flex;
294
+ gap: 15px;
295
+ margin-bottom: 20px;
296
+ }
297
+
298
+ .card {
299
+ flex: 1;
300
+ background: #f8fafc;
301
+ border: 1px solid #e2e8f0;
302
+ border-radius: 15px;
303
+ padding: 20px;
304
+ }
305
+
306
+ .model-label {
307
+ font-size: 11px;
308
+ font-weight: 700;
309
+ color: #94a3b8;
310
+ text-transform: uppercase;
311
+ margin-bottom: 5px;
312
+ }
313
+
314
+ .class-name {
315
+ font-size: 22px;
316
+ font-weight: 700;
317
+ color: #1e293b;
318
+ text-transform: capitalize;
319
+ }
320
+
321
+ .conf-badge {
322
+ font-size: 13px;
323
+ color: #10b981;
324
+ font-weight: 600;
325
+ }
326
+
327
+ .prob-list {
328
+ margin-top: 15px;
329
+ border-top: 1px solid #e2e8f0;
330
+ padding-top: 15px;
331
+ display: flex;
332
+ flex-direction: column;
333
+ gap: 6px;
334
+ max-height: 180px;
335
+ overflow-y: auto;
336
+ }
337
+
338
+ .prob-item {
339
+ display: flex;
340
+ align-items: center;
341
+ font-size: 11px;
342
+ }
343
+
344
+ .prob-label {
345
+ width: 70px;
346
+ text-transform: capitalize;
347
+ font-weight: 500;
348
+ }
349
+
350
+ .prob-bar-bg {
351
+ flex: 1;
352
+ height: 6px;
353
+ background: #e2e8f0;
354
+ border-radius: 3px;
355
+ margin: 0 10px;
356
+ overflow: hidden;
357
+ }
358
+
359
+ .prob-bar-fill {
360
+ height: 100%;
361
+ background: #3b82f6;
362
+ border-radius: 3px;
363
+ transition: width 0.5s;
364
+ }
365
+
366
+ .prob-val {
367
+ width: 35px;
368
+ text-align: right;
369
+ font-weight: 600;
370
+ }
371
+
372
+ .interpretation {
373
+ background: #eff6ff;
374
+ padding: 20px;
375
+ border-radius: 15px;
376
+ border-left: 5px solid #3b82f6;
377
+ margin-top: auto;
378
+ }
379
+
380
+ .inter-title {
381
+ font-weight: 700;
382
+ color: #1e40af;
383
+ margin-bottom: 5px;
384
+ display: flex;
385
+ align-items: center;
386
+ gap: 8px;
387
+ }
388
+
389
+ .inter-text {
390
+ font-size: 13px;
391
+ line-height: 1.5;
392
+ color: #1e3a8a;
393
+ }
394
+
395
+ .loader {
396
+ display: none;
397
+ flex-direction: column;
398
+ align-items: center;
399
+ justify-content: center;
400
+ height: 100%;
401
+ }
402
+
403
+ .spinner {
404
+ width: 40px;
405
+ height: 40px;
406
+ border: 4px solid #f3f3f3;
407
+ border-top: 4px solid #3b82f6;
408
+ border-radius: 50%;
409
+ animation: spin 1s linear infinite;
410
+ margin-bottom: 15px;
411
+ }
412
+
413
+ @keyframes spin {
414
+ 0% { transform: rotate(0deg); }
415
+ 100% { transform: rotate(360deg); }
416
+ }
417
+
418
+ .modal {
419
+ display: none;
420
+ position: fixed;
421
+ top: 0;
422
+ left: 0;
423
+ width: 100%;
424
+ height: 100%;
425
+ background: rgba(0,0,0,0.5);
426
+ backdrop-filter: blur(4px);
427
+ z-index: 2000;
428
+ align-items: center;
429
+ justify-content: center;
430
+ opacity: 0;
431
+ transition: opacity 0.3s;
432
+ }
433
+
434
+ .modal.show {
435
+ opacity: 1;
436
+ }
437
+
438
+ .modal-box {
439
+ background: white;
440
+ padding: 40px;
441
+ border-radius: 20px;
442
+ text-align: center;
443
+ max-width: 450px;
444
+ width: 90%;
445
+ position: relative;
446
+ transform: translateY(20px);
447
+ transition: transform 0.3s;
448
+ }
449
+
450
+ .modal.show .modal-box {
451
+ transform: translateY(0);
452
+ }
453
+
454
+ .close-btn {
455
+ position: absolute;
456
+ top: 15px;
457
+ right: 20px;
458
+ font-size: 28px;
459
+ cursor: pointer;
460
+ color: #94a3b8;
461
+ line-height: 1;
462
+ }
463
+
464
+ .close-btn:hover {
465
+ color: #ef4444;
466
+ }
467
+
468
+ .class-modal-emoji {
469
+ font-size: 60px;
470
+ margin-bottom: 15px;
471
+ }
472
+
473
+ .class-modal-title {
474
+ font-size: 24px;
475
+ font-weight: 700;
476
+ color: #1e293b;
477
+ margin-bottom: 10px;
478
+ }
479
+
480
+ .class-modal-desc {
481
+ color: #475569;
482
+ font-size: 14px;
483
+ line-height: 1.6;
484
+ }
485
+
486
+ ::-webkit-scrollbar { width: 6px; }
487
+ ::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 4px; }
488
+ ::-webkit-scrollbar-thumb { background: #cbd5e0; border-radius: 4px; }
489
+ ::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
490
+ </style>
491
+ </head>
492
+ <body>
493
+
494
+ <section class="hero-section">
495
+ <h1 class="hero-title">Deployment ResNet-MLP dan ResNet-KAN pada CIFAR10</h1>
496
+ <div class="hero-subtitle">Rakyan (NPM)</div>
497
+ </section>
498
+
499
+ <section class="support-section">
500
+ <h3 class="support-title">Penelitian Ini Didukung Oleh</h3>
501
+ <div class="logos-container">
502
+ <div class="logo-item">
503
+ <img src="/static/DSC UI.png" class="logo-img" alt="DSC Logo">
504
+ <p class="logo-text">Data Science Center, FMIPA UI</p>
505
+ </div>
506
+ <div class="logo-item" style="border-left: 2px solid #e2e8f0; padding-left: 40px;">
507
+ <img src="/static/BrainAI.png" class="logo-img" alt="BrainAI Logo">
508
+ <p class="logo-text">BrainAI Lab, Departemen Matematika FMIPA UI</p>
509
+ </div>
510
+ </div>
511
+ </section>
512
+
513
+ <section class="about-section">
514
+ <div class="section-header">
515
+ <h2>About CIFAR-10 Dataset</h2>
516
+ <p>Dataset ini terdiri dari 60.000 citra berwarna 32x32 dalam 10 kelas berbeda.</p>
517
+ </div>
518
+ <div class="class-grid">
519
+ <div class="class-card" onclick="openClassModal('Pesawat', '✈️', 'Citra yang merepresentasikan pesawat terbang, termasuk jet komersial, pesawat tempur, hingga pesawat baling-baling ringan.')">
520
+ <div class="class-emoji">✈️</div>
521
+ <span>Pesawat</span>
522
+ </div>
523
+ <div class="class-card" onclick="openClassModal('Mobil', '🚗', 'Kendaraan roda empat untuk penumpang seperti sedan, hatchback, dan SUV. Kelas ini tidak mencakup truk atau kendaraan alat berat.')">
524
+ <div class="class-emoji">🚗</div>
525
+ <span>Mobil</span>
526
+ </div>
527
+ <div class="class-card" onclick="openClassModal('Burung', '🐦', 'Berbagai spesies burung dari yang berukuran kecil seperti pipit hingga yang besar seperti elang atau unta.')">
528
+ <div class="class-emoji">🐦</div>
529
+ <span>Burung</span>
530
+ </div>
531
+ <div class="class-card" onclick="openClassModal('Kucing', '🐱', 'Mamalia karnivora berukuran kecil dari keluarga Felidae, mencakup berbagai ras kucing domestik.')">
532
+ <div class="class-emoji">🐱</div>
533
+ <span>Kucing</span>
534
+ </div>
535
+ <div class="class-card" onclick="openClassModal('Rusa', '🦌', 'Hewan mamalia pemamah biak yang termasuk dalam famili Cervidae, mencakup berbagai jenis rusa dan kijang.')">
536
+ <div class="class-emoji">🦌</div>
537
+ <span>Rusa</span>
538
+ </div>
539
+ <div class="class-card" onclick="openClassModal('Anjing', '🐶', 'Mamalia karnivora yang telah didomestikasi dari serigala, mencakup berbagai ras dan ukuran.')">
540
+ <div class="class-emoji">🐶</div>
541
+ <span>Anjing</span>
542
+ </div>
543
+ <div class="class-card" onclick="openClassModal('Katak', '🐸', 'Amfibi tak berekor yang pandai melompat, mencakup berbagai spesies katak dan kodok di alam liar.')">
544
+ <div class="class-emoji">🐸</div>
545
+ <span>Katak</span>
546
+ </div>
547
+ <div class="class-card" onclick="openClassModal('Kuda', '🐴', 'Mamalia berkuku satu yang sering digunakan untuk berkuda, pacuan, maupun hewan pekerja beban.')">
548
+ <div class="class-emoji">🐴</div>
549
+ <span>Kuda</span>
550
+ </div>
551
+ <div class="class-card" onclick="openClassModal('Kapal', '🚢', 'Kendaraan air berukuran besar maupun kecil, termasuk kapal pesiar, perahu nelayan, kargo, dan feri.')">
552
+ <div class="class-emoji">🚢</div>
553
+ <span>Kapal</span>
554
+ </div>
555
+ <div class="class-card" onclick="openClassModal('Truk', '🚚', 'Kendaraan bermotor berukuran besar yang dirancang khusus untuk mengangkut barang, muatan, atau kargo berat.')">
556
+ <div class="class-emoji">🚚</div>
557
+ <span>Truk</span>
558
+ </div>
559
+ </div>
560
+ </section>
561
+
562
+ <section class="detection-section">
563
+ <div class="panel">
564
+ <div class="panel-header">
565
+ <div class="panel-svg">
566
+ <svg viewBox="0 0 24 24" width="22" height="22" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
567
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
568
+ <polyline points="17 8 12 3 7 8"></polyline>
569
+ <line x1="12" y1="3" x2="12" y2="15"></line>
570
+ </svg>
571
+ </div>
572
+ Input Citra
573
+ </div>
574
+
575
+ <div class="upload-area" id="drop-zone">
576
+ <div id="prompt" style="text-align: center;">
577
+ <div class="plus-icon">+</div>
578
+ <div class="upload-text-main">Pilih Gambar Objek</div>
579
+ <div class="upload-text-sub">Format: JPG, PNG (Max 5MB)</div>
580
+ </div>
581
+
582
+ <div id="image-preview-container">
583
+ <img id="image-preview" src="#" alt="Preview">
584
+ <button class="btn-change" id="btn-change">Ganti Gambar</button>
585
+ </div>
586
+
587
+ <input type="file" id="file-input" accept="image/jpeg, image/png" hidden>
588
+ </div>
589
+ </div>
590
+
591
+ <div class="panel result-panel">
592
+ <div class="panel-header">
593
+ <div class="panel-svg">
594
+ <svg viewBox="0 0 24 24" width="22" height="22" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
595
+ <polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
596
+ <polyline points="2 17 12 22 22 17"></polyline>
597
+ <polyline points="2 12 12 17 22 12"></polyline>
598
+ </svg>
599
+ </div>
600
+ Hasil ResNet
601
+ </div>
602
+
603
+ <div class="empty-state" id="empty-state">
604
+ <svg class="empty-icon" viewBox="0 0 24 24" stroke-linejoin="round" stroke-linecap="round">
605
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
606
+ </svg>
607
+ <div class="empty-title">Belum Ada Data</div>
608
+ <div class="empty-desc">Silakan unggah gambar objek di panel sebelah kiri untuk memulai proses deteksi.</div>
609
+ </div>
610
+
611
+ <div class="loader" id="loader">
612
+ <div class="spinner"></div>
613
+ <div style="color: #64748b; font-size: 14px;">Memproses prediksi ResNet...</div>
614
+ </div>
615
+
616
+ <div id="results" style="display: none; height: 100%; flex-direction: column;">
617
+ <div class="cards-row">
618
+ <div class="card">
619
+ <p class="model-label">ResNet-MLP</p>
620
+ <p class="class-name" id="mlp-name">-</p>
621
+ <p class="conf-badge">✓ <span id="mlp-conf">-</span>%</p>
622
+ <div class="prob-list" id="mlp-probs"></div>
623
+ </div>
624
+ <div class="card">
625
+ <p class="model-label">ResNet-KAN</p>
626
+ <p class="class-name" id="kan-name">-</p>
627
+ <p class="conf-badge">✓ <span id="kan-conf">-</span>%</p>
628
+ <div class="prob-list" id="kan-probs"></div>
629
+ </div>
630
+ </div>
631
+
632
+ <div class="interpretation" id="inter-box">
633
+ <div class="inter-title" id="inter-title"></div>
634
+ <div class="inter-text" id="inter-text"></div>
635
+ </div>
636
+ </div>
637
+ </div>
638
+ </section>
639
+
640
+ <div class="modal" id="classModal">
641
+ <div class="modal-box">
642
+ <span class="close-btn" onclick="closeModal('classModal')">&times;</span>
643
+ <div class="class-modal-emoji" id="c-emoji"></div>
644
+ <div class="class-modal-title" id="c-title"></div>
645
+ <div class="class-modal-desc" id="c-desc"></div>
646
+ </div>
647
+ </div>
648
+
649
+ <div class="modal" id="warningModal">
650
+ <div class="modal-box">
651
+ <span class="close-btn" onclick="closeModal('warningModal')">&times;</span>
652
+ <h2 style="color: #f59e0b; margin-bottom: 10px; font-weight: 700;">⚠️ Kemungkinan Objek Asing</h2>
653
+ <p style="color: #475569; font-size: 15px; line-height: 1.6;">Citra ini kemungkinan besar berada di luar 10 kelas CIFAR-10 karena tingkat konfidensi yang rendah. Hasil probabilitas di bawah tetap ditampilkan sebagai referensi.</p>
654
+ </div>
655
+ </div>
656
+
657
+ <script>
658
+ const dropZone = document.getElementById('drop-zone');
659
+ const fileInput = document.getElementById('file-input');
660
+ const prompt = document.getElementById('prompt');
661
+ const previewContainer = document.getElementById('image-preview-container');
662
+ const preview = document.getElementById('image-preview');
663
+ const btnChange = document.getElementById('btn-change');
664
+
665
+ const emptyState = document.getElementById('empty-state');
666
+ const loader = document.getElementById('loader');
667
+ const results = document.getElementById('results');
668
+
669
+ dropZone.addEventListener('click', (e) => {
670
+ if (e.target !== btnChange) {
671
+ fileInput.click();
672
+ }
673
+ });
674
+
675
+ btnChange.addEventListener('click', (e) => {
676
+ e.stopPropagation();
677
+ fileInput.click();
678
+ });
679
+
680
+ fileInput.addEventListener('change', (e) => {
681
+ const file = e.target.files[0];
682
+ if (file) handleFile(file);
683
+ });
684
+
685
+ dropZone.addEventListener('dragover', (e) => {
686
+ e.preventDefault();
687
+ dropZone.style.borderColor = '#3b82f6';
688
+ dropZone.style.backgroundColor = '#f8fafc';
689
+ });
690
+
691
+ dropZone.addEventListener('dragleave', () => {
692
+ dropZone.style.borderColor = '#cbd5e0';
693
+ dropZone.style.backgroundColor = '#ffffff';
694
+ });
695
+
696
+ dropZone.addEventListener('drop', (e) => {
697
+ e.preventDefault();
698
+ dropZone.style.borderColor = '#cbd5e0';
699
+ dropZone.style.backgroundColor = '#ffffff';
700
+ if (e.dataTransfer.files.length) {
701
+ fileInput.files = e.dataTransfer.files;
702
+ handleFile(e.dataTransfer.files[0]);
703
+ }
704
+ });
705
+
706
+ function handleFile(file) {
707
+ if (file.type === 'image/jpeg' || file.type === 'image/png') {
708
+ const reader = new FileReader();
709
+ reader.onload = (e) => {
710
+ preview.src = e.target.result;
711
+ prompt.style.display = 'none';
712
+ previewContainer.style.display = 'flex';
713
+ dropZone.style.borderStyle = 'solid';
714
+ upload(file);
715
+ };
716
+ reader.readAsDataURL(file);
717
+ }
718
+ }
719
+
720
+ function renderProbs(containerId, probs) {
721
+ const container = document.getElementById(containerId);
722
+ container.innerHTML = '';
723
+
724
+ probs.forEach(p => {
725
+ let barColor = '#3b82f6';
726
+ if(p.confidence > 70) barColor = '#10b981';
727
+ else if(p.confidence < 15) barColor = '#94a3b8';
728
+
729
+ let indonesianClass = p.class;
730
+ const mapId = {
731
+ 'airplane': 'Pesawat', 'automobile': 'Mobil', 'bird': 'Burung', 'cat': 'Kucing',
732
+ 'deer': 'Rusa', 'dog': 'Anjing', 'frog': 'Katak', 'horse': 'Kuda',
733
+ 'ship': 'Kapal', 'truck': 'Truk'
734
+ };
735
+ if(mapId[p.class]) indonesianClass = mapId[p.class];
736
+
737
+ container.innerHTML += `
738
+ <div class="prob-item">
739
+ <span class="prob-label">${indonesianClass}</span>
740
+ <div class="prob-bar-bg">
741
+ <div class="prob-bar-fill" style="width: ${p.confidence}%; background-color: ${barColor};"></div>
742
+ </div>
743
+ <span class="prob-val">${p.confidence}%</span>
744
+ </div>
745
+ `;
746
+ });
747
+ }
748
+
749
+ function translateClass(enClass) {
750
+ const mapId = {
751
+ 'airplane': 'Pesawat', 'automobile': 'Mobil', 'bird': 'Burung', 'cat': 'Kucing',
752
+ 'deer': 'Rusa', 'dog': 'Anjing', 'frog': 'Katak', 'horse': 'Kuda',
753
+ 'ship': 'Kapal', 'truck': 'Truk'
754
+ };
755
+ return mapId[enClass] || enClass;
756
+ }
757
+
758
+ function upload(file) {
759
+ emptyState.style.display = 'none';
760
+ results.style.display = 'none';
761
+ loader.style.display = 'flex';
762
+
763
+ const formData = new FormData();
764
+ formData.append('file', file);
765
+
766
+ fetch('/api/predict', {
767
+ method: 'POST',
768
+ body: formData
769
+ })
770
+ .then(res => res.json())
771
+ .then(data => {
772
+ loader.style.display = 'none';
773
+
774
+ if (data.status === 'error') {
775
+ alert("Terjadi kesalahan dari server.");
776
+ emptyState.style.display = 'flex';
777
+ return;
778
+ }
779
+
780
+ results.style.display = 'flex';
781
+
782
+ const mlpClass = translateClass(data.model_1.class);
783
+ const kanClass = translateClass(data.model_2.class);
784
+
785
+ document.getElementById('mlp-name').innerText = mlpClass;
786
+ document.getElementById('mlp-conf').innerText = data.model_1.confidence;
787
+ document.getElementById('kan-name').innerText = kanClass;
788
+ document.getElementById('kan-conf').innerText = data.model_2.confidence;
789
+
790
+ renderProbs('mlp-probs', data.model_1.all_probs);
791
+ renderProbs('kan-probs', data.model_2.all_probs);
792
+
793
+ const interBox = document.getElementById('inter-box');
794
+ const it = document.getElementById('inter-title');
795
+ const ix = document.getElementById('inter-text');
796
+
797
+ if(mlpClass === kanClass) {
798
+ interBox.style.backgroundColor = '#f0fdf4';
799
+ interBox.style.borderLeftColor = '#22c55e';
800
+ it.style.color = '#15803d';
801
+ ix.style.color = '#166534';
802
+ it.innerHTML = 'Konsisten';
803
+ ix.innerHTML = `Kedua model bilang bahwa objek ini adalah <strong>${mlpClass}</strong>.`;
804
+ } else {
805
+ interBox.style.backgroundColor = '#fff7ed';
806
+ interBox.style.borderLeftColor = '#f97316';
807
+ it.style.color = '#c2410c';
808
+ ix.style.color = '#9a3412';
809
+ it.innerHTML = 'Perbedaan Prediksi';
810
+ ix.innerHTML = `Model ResNet-MLP bilang ini <strong>${mlpClass}</strong>, sedangkan ResNet-KAN bilang ini <strong>${kanClass}</strong>.`;
811
+ }
812
+
813
+ if (data.is_outlier) {
814
+ showModal('warningModal');
815
+ }
816
+ })
817
+ .catch(err => {
818
+ loader.style.display = 'none';
819
+ emptyState.style.display = 'flex';
820
+ alert("Gagal terhubung ke server.");
821
+ });
822
+ }
823
+
824
+ function openClassModal(title, emoji, desc) {
825
+ document.getElementById('c-title').innerText = title;
826
+ document.getElementById('c-emoji').innerText = emoji;
827
+ document.getElementById('c-desc').innerText = desc;
828
+ showModal('classModal');
829
+ }
830
+
831
+ function showModal(id) {
832
+ const modal = document.getElementById(id);
833
+ modal.style.display = 'flex';
834
+ setTimeout(() => modal.classList.add('show'), 10);
835
+ }
836
+
837
+ function closeModal(id) {
838
+ const modal = document.getElementById(id);
839
+ modal.classList.remove('show');
840
+ setTimeout(() => modal.style.display = 'none', 300);
841
+ }
842
+
843
+ window.onclick = function(event) {
844
+ if (event.target.classList.contains('modal')) {
845
+ closeModal(event.target.id);
846
+ }
847
+ }
848
+ </script>
849
+ </body>
850
+ </html>
weights/convnext_kan_cifar10.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7e53e3ea740d68a17d49ef7943e8fe55a1eb9f5d96d68e8348fe819b8682eb6f
3
+ size 137881637
weights/convnext_mlp_cifar10.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9098c7362c43b1c55fa39b51f5250b02feda11d6a61a485a2977466a9c4c92ae
3
+ size 114020323
weights/resnet_kan_cifar10_run1.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:177e2529170a03a07ea951ed06d57091bcecc08b4dafc06b78e0a20e4c9c9486
3
+ size 147162506
weights/tesresnet_mlp_cifar10_run1.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:faaf2c4e7327148a6cf95c0fa01216cbf592cf786087691374c19c155b8763d4
3
+ size 99648044