rein0421 commited on
Commit
4e8d134
·
verified ·
1 Parent(s): e3bb217

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +348 -136
app.py CHANGED
@@ -903,170 +903,382 @@ async def read_root():
903
  <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
904
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
905
  <style>
906
- body { background-color: #f0f0f5; color: #333; text-align: center; padding: 40px 20px; }
907
- h1 { color: #555; margin-bottom: 30px; font-weight: bold; }
908
- .image-preview, .processed-preview { max-width: 100%; height: auto; border-radius: 10px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); margin-top: 20px; }
909
- #result { margin-top: 40px; display: none; }
910
- .slider-container { text-align: left; margin-top: 20px; }
911
- .slider-label { font-size: 1.2rem; color: #333; }
912
- #slider { margin-top: 10px; }
913
- .btn-primary { background-color: #007bff; border-color: #007bff; font-size: 1.2rem; padding: 10px 20px; border-radius: 50px; }
914
- .btn-primary:hover { background-color: #0056b3; border-color: #004085; }
915
- .form-control, .custom-select { border-radius: 20px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); }
916
- .form-control-file { font-size: 1rem; }
917
- .form-group { margin-bottom: 25px; }
918
- .btn-success { padding: 10px 20px; border-radius: 50px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
919
  #loadingSpinner {
920
  position: fixed;
921
  top: 50%;
922
  left: 50%;
923
  transform: translate(-50%, -50%);
924
- color: #007bff;
925
- text-align: center;
 
 
926
  z-index: 9999;
927
  }
928
  </style>
929
  </head>
930
  <body>
931
- <div id="loadingSpinner" style="display: none;">
932
- <i class="fas fa-spinner fa-spin fa-3x"></i>
933
- <p>画像を処理中です。少々お待ちください...</p>
934
- </div>
 
935
  <div class="container">
936
- <h1><i class="fas fa-image"></i> 画像処理アプリ - モザイクとインペイント</h1>
937
- <div class="form-group">
938
- <input type="file" id="uploadImage" class="form-control-file" accept="image/*" onchange="previewAndResizeImage()">
939
- </div>
940
- <img id="uploadedImage" class="image-preview" src="#" alt="アップロードされた画像" style="display: none;">
941
- <div class="form-group mt-4">
942
- <label for="processingType">処理方法を選択:</label>
943
- <select id="processingType" class="custom-select">
944
- <option value="opencv">OpenCVインペイント</option>
945
- <option value="simple_lama">Simple Lamaインペイント</option>
946
- <option value="stamp">stampインペイント</option>
947
- <option value="mosaic">mosaicインペイント</option>
948
- </select>
949
- </div>
950
- <div class="slider-container">
951
- <label for="riskLevel" class="slider-label">リスクレベル (0-100): <span id="riskLevelLabel">50</span></label>
952
- <div id="slider"></div>
953
- </div>
954
- <button class="btn btn-primary mt-4" onclick="processImage()">処理開始</button>
955
- <div id="result" class="mt-5">
956
- <h2>処理結果:</h2>
957
- <img id="processedImage" class="processed-preview" src="" alt="">
958
- <a id="downloadLink" class="btn btn-success mt-3" href="#" download="processed_image.jpg">処理された画像をダウンロード</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
959
  </div>
960
  </div>
 
961
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
962
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
963
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
964
  <script>
965
- $(function() {
966
- $("#slider").slider({
967
- range: "min",
968
- value: 50,
969
- min: 0,
970
- max: 100,
971
- slide: function(event, ui) {
972
- $("#riskLevelLabel").text(ui.value);
973
- }
 
 
 
974
  });
975
- });
976
- let resizedImageBlob = null; // 縮小された画像データを保持
977
- function previewAndResizeImage() {
978
- const fileInput = document.getElementById('uploadImage');
979
- const uploadedImage = document.getElementById('uploadedImage');
980
-
981
- if (fileInput.files && fileInput.files[0]) {
982
- const reader = new FileReader();
983
- reader.onload = function(e) {
984
- const img = new Image();
985
- img.onload = function() {
986
- const maxWidth = 600;
987
- const maxHeight = 600;
988
- let width = img.width;
989
- let height = img.height;
990
- // 縦横比を維持して画像を縮小
991
- if (width > maxWidth || height > maxHeight) {
992
- const ratio = Math.min(maxWidth / width, maxHeight / height);
993
- width = width * ratio;
994
- height = height * ratio;
995
- }
996
- const canvas = document.createElement('canvas');
997
- canvas.width = width;
998
- canvas.height = height;
999
- const ctx = canvas.getContext('2d');
1000
- ctx.drawImage(img, 0, 0, width, height);
1001
-
1002
- // 縮小された画像のプレビュー表示
1003
- uploadedImage.src = canvas.toDataURL('image/jpeg');
1004
- uploadedImage.style.display = 'block';
1005
- // Blobに変換してサーバーに送信できるように保存
1006
- canvas.toBlob((blob) => {
1007
- resizedImageBlob = blob; // サーバー送信用の縮小画像データを保存
1008
- }, 'image/jpeg');
 
 
 
 
 
 
 
 
 
1009
  };
1010
- img.src = e.target.result;
1011
- };
1012
- reader.readAsDataURL(fileInput.files[0]);
1013
  }
1014
- }
1015
- function processImage() {
1016
- const processingType = document.getElementById('processingType').value;
1017
- const riskLevel = $("#slider").slider("value");
1018
- const resultDiv = document.getElementById('result');
1019
- const processedImage = document.getElementById('processedImage');
1020
- const downloadLink = document.getElementById('downloadLink');
1021
- if (!resizedImageBlob) {
1022
- alert("画像を選択して縮小してください。");
1023
- return;
 
 
1024
  }
1025
- // ローディングスピナーを表示
1026
- document.getElementById('loadingSpinner').style.display = 'block';
1027
- const formData = new FormData();
1028
- formData.append('image', resizedImageBlob, 'resized_image.jpg');
1029
- formData.append('risk_level', riskLevel);
1030
- let apiEndpoint;
1031
- if (processingType === "opencv") {
1032
- apiEndpoint = "/create-mask-and-inpaint-opencv";
1033
- } else if (processingType === "simple_lama") {
1034
- apiEndpoint = "/create-mask-and-inpaint-simple-lama";
1035
- } else if (processingType === "stamp") {
1036
- apiEndpoint = "/create-mask-and-inpaint-stamp";
1037
- } else if (processingType == "mosaic") {
1038
- apiEndpoint = "/create-mask-and-inpaint-mosaic";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1039
  }
1040
- const url = "https://rein0421-aidentify.hf.space" + apiEndpoint;
1041
- fetch(url, {
1042
- method: 'POST',
1043
- body: formData
1044
- })
1045
- .then(response => {
1046
- if (!response.ok) {
1047
- throw new Error("Network response was not ok");
1048
  }
1049
- return response.blob();
1050
- })
1051
- .then(blob => {
1052
- const objectURL = URL.createObjectURL(blob);
1053
- processedImage.src = objectURL;
1054
- downloadLink.href = objectURL;
1055
- resultDiv.style.display = "block";
1056
- })
1057
- .catch(error => {
1058
- console.error("画像処理に失敗しました。", error);
1059
- alert("画像処理に失敗しました。");
1060
- })
1061
- .finally(() => {
1062
- // ローディングスピナーを非表示にする
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1063
  document.getElementById('loadingSpinner').style.display = 'none';
 
 
 
 
 
 
 
 
1064
  });
1065
- }
1066
  </script>
1067
  </body>
1068
  </html>
1069
-
1070
  """
1071
  return HTMLResponse(content=html_content)
1072
 
 
903
  <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
904
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
905
  <style>
906
+ body {
907
+ background-color: #f0f0f5;
908
+ color: #333;
909
+ padding: 40px 20px;
910
+ }
911
+ h1 {
912
+ color: #555;
913
+ margin-bottom: 30px;
914
+ font-weight: bold;
915
+ text-align: center;
916
+ }
917
+ .image-preview, .processed-preview {
918
+ max-width: 100%;
919
+ height: auto;
920
+ border-radius: 10px;
921
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
922
+ margin-top: 20px;
923
+ }
924
+ #result {
925
+ margin-top: 40px;
926
+ display: none;
927
+ }
928
+ .slider-container {
929
+ text-align: left;
930
+ margin-top: 20px;
931
+ }
932
+ .slider-label {
933
+ font-size: 1.2rem;
934
+ color: #333;
935
+ }
936
+ .btn-primary {
937
+ background-color: #007bff;
938
+ border-color: #007bff;
939
+ font-size: 1.2rem;
940
+ padding: 10px 20px;
941
+ border-radius: 50px;
942
+ }
943
+ .btn-primary:hover {
944
+ background-color: #0056b3;
945
+ border-color: #004085;
946
+ }
947
+ .form-control, .custom-select {
948
+ border-radius: 20px;
949
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
950
+ }
951
+ .nav-tabs {
952
+ margin-bottom: 30px;
953
+ border-bottom: 2px solid #007bff;
954
+ }
955
+ .nav-tabs .nav-link {
956
+ border: none;
957
+ color: #555;
958
+ font-size: 1.1rem;
959
+ padding: 12px 25px;
960
+ border-radius: 10px 10px 0 0;
961
+ }
962
+ .nav-tabs .nav-link.active {
963
+ background-color: #007bff;
964
+ color: white;
965
+ }
966
+ .tab-content {
967
+ padding: 20px;
968
+ background: white;
969
+ border-radius: 0 0 10px 10px;
970
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
971
+ }
972
  #loadingSpinner {
973
  position: fixed;
974
  top: 50%;
975
  left: 50%;
976
  transform: translate(-50%, -50%);
977
+ background: rgba(255,255,255,0.9);
978
+ padding: 20px;
979
+ border-radius: 10px;
980
+ box-shadow: 0 0 15px rgba(0,0,0,0.2);
981
  z-index: 9999;
982
  }
983
  </style>
984
  </head>
985
  <body>
986
+ <div id="loadingSpinner" style="display: none;">
987
+ <i class="fas fa-spinner fa-spin fa-3x"></i>
988
+ <p>画像を処理中です。少々お待ちください...</p>
989
+ </div>
990
+
991
  <div class="container">
992
+ <h1><i class="fas fa-image"></i> 画像処理アプリ</h1>
993
+
994
+ <ul class="nav nav-tabs" role="tablist">
995
+ <li class="nav-item">
996
+ <a class="nav-link active" id="general-tab" data-toggle="tab" href="#general" role="tab">
997
+ <i class="fas fa-magic"></i> 一般処理モード
998
+ </a>
999
+ </li>
1000
+ <li class="nav-item">
1001
+ <a class="nav-link" id="face-tab" data-toggle="tab" href="#face" role="tab">
1002
+ <i class="fas fa-user-circle"></i> 顔照合モード
1003
+ </a>
1004
+ </li>
1005
+ </ul>
1006
+
1007
+ <div class="tab-content">
1008
+ <!-- 一般処理モード -->
1009
+ <div class="tab-pane fade show active" id="general" role="tabpanel">
1010
+ <div class="form-group">
1011
+ <label for="uploadImage1">画像をアップロード:</label>
1012
+ <input type="file" id="uploadImage1" class="form-control-file" accept="image/*" onchange="previewAndResizeImage('uploadImage1', 'uploadedImage1')">
1013
+ </div>
1014
+ <img id="uploadedImage1" class="image-preview" src="#" alt="アップロードされた画像" style="display: none;">
1015
+
1016
+ <div class="form-group mt-4">
1017
+ <label for="processingType">処理方法を選択:</label>
1018
+ <select id="processingType" class="custom-select">
1019
+ <option value="opencv">OpenCVインペイント</option>
1020
+ <option value="simple_lama">Simple Lamaインペイント</option>
1021
+ <option value="stamp">stampインペイント</option>
1022
+ <option value="mosaic">mosaicインペイント</option>
1023
+ </select>
1024
+ </div>
1025
+
1026
+ <div class="slider-container">
1027
+ <label for="riskLevel1" class="slider-label">リスクレベル (0-100): <span id="riskLevelLabel1">50</span></label>
1028
+ <div id="slider1"></div>
1029
+ </div>
1030
+
1031
+ <button class="btn btn-primary mt-4" onclick="processGeneralImage()">処理開始</button>
1032
+ </div>
1033
+
1034
+ <!-- 顔照合モード -->
1035
+ <div class="tab-pane fade" id="face" role="tabpanel">
1036
+ <div class="form-group">
1037
+ <label for="uploadImage2">処理する画像をアップロード:</label>
1038
+ <input type="file" id="uploadImage2" class="form-control-file" accept="image/*" onchange="previewAndResizeImage('uploadImage2', 'uploadedImage2')">
1039
+ </div>
1040
+ <img id="uploadedImage2" class="image-preview" src="#" alt="アップロードされた画像" style="display: none;">
1041
+
1042
+ <div class="form-group mt-4">
1043
+ <label for="faceOption">自分の顔の入力方法を選択:</label>
1044
+ <select id="faceOption" class="custom-select" onchange="toggleFaceInput()">
1045
+ <option value="upload">ファイルからアップロード</option>
1046
+ <option value="camera">カメラで撮影</option>
1047
+ </select>
1048
+ </div>
1049
+
1050
+ <div class="form-group" id="uploadFaceGroup">
1051
+ <label for="uploadFace">顔画像をアップロード:</label>
1052
+ <input type="file" id="uploadFace" class="form-control-file" accept="image/*" onchange="previewFaceImage()">
1053
+ <img id="facePreview" class="image-preview" src="#" alt="顔画像のプレビュー" style="display: none;">
1054
+ </div>
1055
+
1056
+ <div class="form-group" id="cameraFaceGroup" style="display: none;">
1057
+ <video id="cameraStream" width="100%" autoplay></video>
1058
+ <button class="btn btn-secondary mt-2" onclick="captureFaceImage()">顔をキャプチャ</button>
1059
+ <canvas id="cameraCanvas" style="display: none;"></canvas>
1060
+ </div>
1061
+
1062
+ <div class="slider-container">
1063
+ <label for="riskLevel2" class="slider-label">リスクレベル (0-100): <span id="riskLevelLabel2">50</span></label>
1064
+ <div id="slider2"></div>
1065
+ </div>
1066
+
1067
+ <button class="btn btn-primary mt-4" onclick="processFaceImage()">処理開始</button>
1068
+ </div>
1069
+
1070
+ <!-- 処理結果(共通) -->
1071
+ <div id="result" class="mt-5">
1072
+ <h2>��理結果:</h2>
1073
+ <img id="processedImage" class="processed-preview" src="" alt="">
1074
+ <a id="downloadLink" class="btn btn-success mt-3" href="#" download="processed_image.jpg">処理された画像をダウンロード</a>
1075
+ </div>
1076
  </div>
1077
  </div>
1078
+
1079
  <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
1080
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
1081
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
1082
  <script>
1083
+ // スライダーの初期化
1084
+ $(function() {
1085
+ $("#slider1, #slider2").slider({
1086
+ range: "min",
1087
+ value: 50,
1088
+ min: 0,
1089
+ max: 100,
1090
+ slide: function(event, ui) {
1091
+ const labelId = $(this).attr('id') === 'slider1' ? 'riskLevelLabel1' : 'riskLevelLabel2';
1092
+ $("#" + labelId).text(ui.value);
1093
+ }
1094
+ });
1095
  });
1096
+
1097
+ let resizedImageBlob1 = null;
1098
+ let resizedImageBlob2 = null;
1099
+ let faceImageBlob = null;
1100
+
1101
+ function previewAndResizeImage(inputId, imageId) {
1102
+ const fileInput = document.getElementById(inputId);
1103
+ const uploadedImage = document.getElementById(imageId);
1104
+
1105
+ if (fileInput.files && fileInput.files[0]) {
1106
+ const reader = new FileReader();
1107
+ reader.onload = function(e) {
1108
+ const img = new Image();
1109
+ img.onload = function() {
1110
+ const maxWidth = 1200;
1111
+ const maxHeight = 1200;
1112
+ let width = img.width;
1113
+ let height = img.height;
1114
+
1115
+ if (width > maxWidth || height > maxHeight) {
1116
+ const ratio = Math.min(maxWidth / width, maxHeight / height);
1117
+ width *= ratio;
1118
+ height *= ratio;
1119
+ }
1120
+
1121
+ const canvas = document.createElement('canvas');
1122
+ canvas.width = width;
1123
+ canvas.height = height;
1124
+ const ctx = canvas.getContext('2d');
1125
+ ctx.drawImage(img, 0, 0, width, height);
1126
+
1127
+ uploadedImage.src = canvas.toDataURL('image/jpeg');
1128
+ uploadedImage.style.display = 'block';
1129
+
1130
+ canvas.toBlob((blob) => {
1131
+ if (inputId === 'uploadImage1') {
1132
+ resizedImageBlob1 = blob;
1133
+ } else {
1134
+ resizedImageBlob2 = blob;
1135
+ }
1136
+ }, 'image/jpeg');
1137
+ };
1138
+ img.src = e.target.result;
1139
  };
1140
+ reader.readAsDataURL(fileInput.files[0]);
1141
+ }
 
1142
  }
1143
+
1144
+ function previewFaceImage() {
1145
+ const fileInput = document.getElementById('uploadFace');
1146
+ if (fileInput.files && fileInput.files[0]) {
1147
+ const reader = new FileReader();
1148
+ reader.onload = function(e) {
1149
+ document.getElementById('facePreview').src = e.target.result;
1150
+ document.getElementById('facePreview').style.display = 'block';
1151
+ };
1152
+ reader.readAsDataURL(fileInput.files[0]);
1153
+ faceImageBlob = fileInput.files[0];
1154
+ }
1155
  }
1156
+
1157
+ function toggleFaceInput() {
1158
+ const faceOption = document.getElementById('faceOption').value;
1159
+ document.getElementById('uploadFaceGroup').style.display = faceOption === 'upload' ? 'block' : 'none';
1160
+ document.getElementById('cameraFaceGroup').style.display = faceOption === 'camera' ? 'block' : 'none';
1161
+ if (faceOption === 'camera') {
1162
+ startCamera();
1163
+ } else {
1164
+ stopCamera();
1165
+ }
1166
+ }
1167
+
1168
+ function startCamera() {
1169
+ const video = document.getElementById('cameraStream');
1170
+ navigator.mediaDevices.getUserMedia({ video: true })
1171
+ .then(stream => video.srcObject = stream)
1172
+ .catch(error => console.error('カメラの起動に失敗しました:', error));
1173
+ }
1174
+
1175
+ function stopCamera() {
1176
+ const video = document.getElementById('cameraStream');
1177
+ const stream = video.srcObject;
1178
+ if (stream) {
1179
+ stream.getTracks().forEach(track => track.stop());
1180
+ video.srcObject = null;
1181
+ }
1182
+ }
1183
+
1184
+ function captureFaceImage() {
1185
+ const video = document.getElementById('cameraStream');
1186
+ const canvas = document.getElementById('cameraCanvas');
1187
+ const context = canvas.getContext('2d');
1188
+ canvas.width = video.videoWidth;
1189
+ canvas.height = video.videoHeight;
1190
+ context.drawImage(video, 0, 0, canvas.width, canvas.height);
1191
+ canvas.toBlob(blob => faceImageBlob = blob, 'image/jpeg');
1192
+ stopCamera();
1193
+ }
1194
+
1195
+ function processGeneralImage() {
1196
+ if (!resizedImageBlob1) {
1197
+ alert("画像を選択してください。");
1198
+ return;
1199
+ }
1200
+
1201
+ const processingType = document.getElementById('processingType').value;
1202
+ const riskLevel = $("#slider1").slider("value");
1203
+ showLoadingSpinner();
1204
+
1205
+ const formData = new FormData();
1206
+ formData.append('image', resizedImageBlob1, 'resized_image.jpg');
1207
+ formData.append('risk_level', riskLevel);
1208
+
1209
+ let apiEndpoint;
1210
+ if (processingType === "opencv") {
1211
+ apiEndpoint = "/create-mask-and-inpaint-opencv";
1212
+ } else if (processingType === "simple_lama") {
1213
+ apiEndpoint = "/create-mask-and-inpaint-simple-lama";
1214
+ } else if (processingType === "stamp") {
1215
+ apiEndpoint = "/create-mask-and-inpaint-stamp";
1216
+ } else if (processingType === "mosaic") {
1217
+ apiEndpoint = "/create-mask-and-inpaint-mosaic";
1218
+ }
1219
+
1220
+ processImageRequest(formData, "https://rein0421-aidentify.hf.space" + apiEndpoint);
1221
  }
1222
+
1223
+ function processFaceImage() {
1224
+ if (!resizedImageBlob2 || !faceImageBlob) {
1225
+ alert("処理する画像と顔画像の両方を設定してください。");
1226
+ return;
 
 
 
1227
  }
1228
+
1229
+ const riskLevel = $("#slider2").slider("value");
1230
+ showLoadingSpinner();
1231
+
1232
+ const formData = new FormData();
1233
+ formData.append('reference_image', faceImageBlob, 'reference_image.jpg');
1234
+ formData.append('test_image', resizedImageBlob2, 'test_image.jpg');
1235
+ formData.append('risk_level', riskLevel);
1236
+
1237
+ processImageRequest(formData, "https://rein0421-aidentify.hf.space/mosaic_faces");
1238
+ }
1239
+
1240
+ function processImageRequest(formData, url) {
1241
+ fetch(url, {
1242
+ method: 'POST',
1243
+ body: formData
1244
+ })
1245
+ .then(response => {
1246
+ if (!response.ok) throw new Error("Network response was not ok");
1247
+ return response.blob();
1248
+ })
1249
+ .then(blob => {
1250
+ const objectURL = URL.createObjectURL(blob);
1251
+ document.getElementById('processedImage').src = objectURL;
1252
+ document.getElementById('downloadLink').href = objectURL;
1253
+ document.getElementById('result').style.display = "block";
1254
+ })
1255
+ .catch(error => {
1256
+ console.error("画像処理に失敗しました。", error);
1257
+ alert("画像処理に失敗しました。");
1258
+ })
1259
+ .finally(() => {
1260
+ hideLoadingSpinner();
1261
+ });
1262
+ }
1263
+
1264
+ function showLoadingSpinner() {
1265
+ document.getElementById('loadingSpinner').style.display = 'block';
1266
+ }
1267
+
1268
+ function hideLoadingSpinner() {
1269
  document.getElementById('loadingSpinner').style.display = 'none';
1270
+ }
1271
+
1272
+ // タブ切り替え時の処理
1273
+ $('.nav-tabs a').on('shown.bs.tab', function (e) {
1274
+ // 結果表示をリセット
1275
+ document.getElementById('result').style.display = 'none';
1276
+ document.getElementById('processedImage').src = '';
1277
+ document.getElementById('downloadLink').href = '#';
1278
  });
 
1279
  </script>
1280
  </body>
1281
  </html>
 
1282
  """
1283
  return HTMLResponse(content=html_content)
1284