syurein commited on
Commit
fd6583c
·
1 Parent(s): f695a59

とりあえず整理

Browse files
Files changed (1) hide show
  1. app.py +5 -509
app.py CHANGED
@@ -11,7 +11,7 @@
11
 
12
 
13
 
14
-
15
  import os
16
  import supervision as sv
17
  from PIL import Image, ImageFilter
@@ -77,7 +77,7 @@ app.add_middleware(
77
 
78
 
79
  HOME = "./"
80
-
81
  dangerarray=[10,30,90,50,80,20,40,70,100,60]#ここに各クラスターの危険度を設定しておく
82
  #ここで認識する精度を上げたり下げたりできる
83
 
@@ -754,7 +754,7 @@ async def create_mask_and_inpaint_simple_lama(
754
 
755
  # Save the input image
756
  input_path = save_image(image.file, "input.jpg")
757
- print('1111',point1,point2)
758
  # Create a mask image (using the new process_image function)
759
  mask_path = special_process_image_yolo(risk_level, input_path, point1, point2,thresholds=thresholds)
760
 
@@ -790,7 +790,7 @@ async def create_mask_sum(image: UploadFile = File(...), risk_level: int = Form(
790
  input_path = save_image(image.file, f"./input_{timestamp}_{unique_id}.jpg")
791
  mask_path = special_process_image_yolo(risk_level, input_path, point1, point2,thresholds=thresholds)
792
  output_path = f"./output_simple_lama_{timestamp}_{unique_id}.jpg"
793
-
794
  # OpenCVでインペイント
795
  inpaint_image_with_mask1(input_path, mask_path, output_path)
796
 
@@ -902,509 +902,5 @@ async def mosaic_faces(reference_image: UploadFile = File(...), test_image: Uplo
902
 
903
  @app.get("/", response_class=HTMLResponse)
904
  async def read_root():
905
- html_content = """
906
-
907
- <!DOCTYPE html>
908
- <html lang="ja">
909
- <head>
910
- <meta charset="UTF-8">
911
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
912
- <title>画像処理アプリ</title>
913
- <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
914
- <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
915
- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
916
- <style>
917
- /* 既存のスタイル */
918
- body {
919
- background-color: #f0f0f5;
920
- color: #333;
921
- padding: 40px 20px;
922
- }
923
- /* ... 他の既存のスタイル ... */
924
-
925
- /* ヘルプボタンのスタイル */
926
- .help-button {
927
- position: fixed;
928
- bottom: 20px;
929
- right: 20px;
930
- width: 50px;
931
- height: 50px;
932
- border-radius: 50%;
933
- background-color: #007bff;
934
- color: white;
935
- border: none;
936
- box-shadow: 0 2px 10px rgba(0,0,0,0.2);
937
- z-index: 1000;
938
- display: flex;
939
- align-items: center;
940
- justify-content: center;
941
- font-size: 1.5rem;
942
- }
943
-
944
- .help-button:hover {
945
- background-color: #0056b3;
946
- }
947
-
948
- /* モーダル内のスタイル */
949
- .help-section {
950
- margin-bottom: 30px;
951
- padding: 15px;
952
- border-radius: 8px;
953
- background-color: #f8f9fa;
954
- }
955
-
956
- .help-section h4 {
957
- color: #007bff;
958
- margin-bottom: 15px;
959
- }
960
-
961
- .step-list {
962
- padding-left: 20px;
963
- }
964
-
965
- .step-list li {
966
- margin-bottom: 10px;
967
- }
968
-
969
- .modal-body img {
970
- max-width: 100%;
971
- border-radius: 5px;
972
- margin: 10px 0;
973
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
974
- }
975
- </style>
976
-
977
-
978
- <style>
979
- body {
980
- background-color: #f0f0f5;
981
- color: #333;
982
- padding: 40px 20px;
983
- }
984
- h1 {
985
- color: #555;
986
- margin-bottom: 30px;
987
- font-weight: bold;
988
- text-align: center;
989
- }
990
- .image-preview, .processed-preview {
991
- max-width: 100%;
992
- height: auto;
993
- border-radius: 10px;
994
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
995
- margin-top: 20px;
996
- }
997
- #result {
998
- margin-top: 40px;
999
- display: none;
1000
- }
1001
- .slider-container {
1002
- text-align: left;
1003
- margin-top: 20px;
1004
- }
1005
- .slider-label {
1006
- font-size: 1.2rem;
1007
- color: #333;
1008
- }
1009
- .btn-primary {
1010
- background-color: #007bff;
1011
- border-color: #007bff;
1012
- font-size: 1.2rem;
1013
- padding: 10px 20px;
1014
- border-radius: 50px;
1015
- }
1016
- .btn-primary:hover {
1017
- background-color: #0056b3;
1018
- border-color: #004085;
1019
- }
1020
- .form-control, .custom-select {
1021
- border-radius: 20px;
1022
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
1023
- }
1024
- .nav-tabs {
1025
- margin-bottom: 30px;
1026
- border-bottom: 2px solid #007bff;
1027
- }
1028
- .nav-tabs .nav-link {
1029
- border: none;
1030
- color: #555;
1031
- font-size: 1.1rem;
1032
- padding: 12px 25px;
1033
- border-radius: 10px 10px 0 0;
1034
- }
1035
- .nav-tabs .nav-link.active {
1036
- background-color: #007bff;
1037
- color: white;
1038
- }
1039
- .tab-content {
1040
- padding: 20px;
1041
- background: white;
1042
- border-radius: 0 0 10px 10px;
1043
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
1044
- }
1045
- #loadingSpinner {
1046
- position: fixed;
1047
- top: 50%;
1048
- left: 50%;
1049
- transform: translate(-50%, -50%);
1050
- background: rgba(255,255,255,0.9);
1051
- padding: 20px;
1052
- border-radius: 10px;
1053
- box-shadow: 0 0 15px rgba(0,0,0,0.2);
1054
- z-index: 9999;
1055
- }
1056
- </style>
1057
- </head>
1058
- <body>
1059
- <div id="loadingSpinner" style="display: none;">
1060
- <i class="fas fa-spinner fa-spin fa-3x"></i>
1061
- <p>画像を処理中です。少々お待ちください...</p>
1062
- </div>
1063
- <div class="container">
1064
- <h1><i class="fas fa-image"></i> AIデンティファイ</h1>
1065
-
1066
- <ul class="nav nav-tabs" role="tablist">
1067
- <li class="nav-item">
1068
- <a class="nav-link active" id="general-tab" data-toggle="tab" href="#general" role="tab">
1069
- <i class="fas fa-magic"></i> 個人情報保護モード
1070
- </a>
1071
- </li>
1072
- <li class="nav-item">
1073
- <a class="nav-link" id="face-tab" data-toggle="tab" href="#face" role="tab">
1074
- <i class="fas fa-user-circle"></i> 顔保護モード
1075
- </a>
1076
- </li>
1077
- </ul>
1078
- <div class="tab-content">
1079
- <!-- 一般処理モード -->
1080
- <div class="tab-pane fade show active" id="general" role="tabpanel">
1081
- <div class="form-group">
1082
- <label for="uploadImage1">画像をアップロード:</label>
1083
- <input type="file" id="uploadImage1" class="form-control-file" accept="image/*" onchange="previewAndResizeImage('uploadImage1', 'uploadedImage1')">
1084
- </div>
1085
- <img id="uploadedImage1" class="image-preview" src="#" alt="アップロードされた画像" style="display: none;">
1086
-
1087
- <div class="form-group mt-4">
1088
- <label for="processingType">処理方法を選択:</label>
1089
- <select id="processingType" class="custom-select">
1090
- <option value="opencv">OpenCVインペイント</option>
1091
- <option value="simple_lama">Simple Lamaインペイント</option>
1092
- <option value="stamp">stampインペイント</option>
1093
- <option value="mosaic">mosaicインペイント</option>
1094
- </select>
1095
- </div>
1096
- <div class="slider-container">
1097
- <label for="riskLevel1" class="slider-label">リスクレベル (0-100): <span id="riskLevelLabel1">50</span></label>
1098
- <div id="slider1"></div>
1099
- </div>
1100
- <button class="btn btn-primary mt-4" onclick="processGeneralImage()">処理開始</button>
1101
- </div>
1102
- <!-- 顔照合モード -->
1103
- <div class="tab-pane fade" id="face" role="tabpanel">
1104
- <div class="form-group">
1105
- <label for="uploadImage2">処理する画像をアップロード:</label>
1106
- <input type="file" id="uploadImage2" class="form-control-file" accept="image/*" onchange="previewAndResizeImage('uploadImage2', 'uploadedImage2')">
1107
- </div>
1108
- <img id="uploadedImage2" class="image-preview" src="#" alt="アップロードされた画像" style="display: none;">
1109
- <div class="form-group mt-4">
1110
- <label for="faceOption">自分の顔の入力方法を選択:</label>
1111
- <select id="faceOption" class="custom-select" onchange="toggleFaceInput()">
1112
- <option value="upload">ファイルからアップロード</option>
1113
- <option value="camera">カメラで撮影</option>
1114
- </select>
1115
- </div>
1116
- <div class="form-group" id="uploadFaceGroup">
1117
- <label for="uploadFace">顔画像をアップロード:</label>
1118
- <input type="file" id="uploadFace" class="form-control-file" accept="image/*" onchange="previewFaceImage()">
1119
- <img id="facePreview" class="image-preview" src="#" alt="顔画像のプレビュー" style="display: none;">
1120
- </div>
1121
- <div class="form-group" id="cameraFaceGroup" style="display: none;">
1122
- <video id="cameraStream" width="100%" autoplay></video>
1123
- <button class="btn btn-secondary mt-2" onclick="captureFaceImage()">顔をキャプチャ</button>
1124
- <canvas id="cameraCanvas" style="display: none;"></canvas>
1125
- </div>
1126
- <div class="slider-container">
1127
- <label for="riskLevel2" class="slider-label">リスクレベル (0-100): <span id="riskLevelLabel2">50</span></label>
1128
- <div id="slider2"></div>
1129
- </div>
1130
- <button class="btn btn-primary mt-4" onclick="processFaceImage()">処理開始</button>
1131
- </div>
1132
- <!-- 処理結果(共通) -->
1133
- <div id="result" class="mt-5">
1134
- <h2>処理結果:</h2>
1135
- <img id="processedImage" class="processed-preview" src="" alt="">
1136
- <a id="downloadLink" class="btn btn-success mt-3" href="#" download="processed_image.jpg">処理された画像をダウンロード</a>
1137
- </div>
1138
- </div>
1139
- </div>
1140
-
1141
-
1142
- <!-- ヘルプボタン -->
1143
- <button class="help-button" data-toggle="modal" data-target="#helpModal">
1144
- <i class="fas fa-question"></i>
1145
- </button>
1146
-
1147
- <!-- ヘルプモーダル -->
1148
- <div class="modal fade" id="helpModal" tabindex="-1" role="dialog" aria-labelledby="helpModalLabel" aria-hidden="true">
1149
- <div class="modal-dialog modal-lg" role="document">
1150
- <div class="modal-content">
1151
- <div class="modal-header">
1152
- <h5 class="modal-title" id="helpModalLabel">
1153
- <i class="fas fa-question-circle"></i> AIデンティファイの使い方
1154
- </h5>
1155
- <button type="button" class="close" data-dismiss="modal" aria-label="Close">
1156
- <span aria-hidden="true">&times;</span>
1157
- </button>
1158
- </div>
1159
- <div class="modal-body">
1160
- <!-- 一般処理モードの説明 -->
1161
- <div class="help-section">
1162
- <h4><i class="fas fa-magic"></i> 個人情報保護モードの使い方</h4>
1163
- <ol class="step-list">
1164
-   <li>このモードではあなたの個人情報を簡単に保護することができます</li>
1165
- <li>「画像をアップロード」ボタンをクリックして、処理したい画像を選択します。</li>
1166
- <li>処理方法を以下から選択します:
1167
- <ul>
1168
- <li><strong>OpenCVインペイント:</strong> 一般的な画像補正</li>
1169
- <li><strong>Simple Lamaインペイント:</strong> 高度な画像補正</li>
1170
- <li><strong>stampインペイント:</strong> スタンプによる画像加工</li>
1171
- <li><strong>mosaicインペイント:</strong> モザイクによる加工</li>
1172
- </ul>
1173
- </li>
1174
- <li>スライダーでリスクレベルを調整します(0-100):
1175
- <ul>
1176
- <li>値が高いほど、より強い保護が適用されます</li>
1177
- <li>推奨値は30前後です</li>
1178
- </ul>
1179
- </li>
1180
- <li>「処理開始」ボタンをクリックして処理を実行します。</li>
1181
- </ol>
1182
- </div>
1183
-
1184
- <!-- 顔照合モードの説明 -->
1185
- <div class="help-section">
1186
- <h4><i class="fas fa-user-circle"></i> 顔保護モードの使い方</h4>
1187
- <ol class="step-list">
1188
-   <li>このモードではあなたの顔以外を隠すことができます。</li>
1189
- <li>「処理する画像をアップロード」から、処理したい画像を選択します。</li>
1190
- <li>自分の顔の入力方法を選択します:
1191
- <ul>
1192
- <li><strong>ファイルからアップロード:</strong> 既存の顔写真を使用</li>
1193
- <li><strong>カメラで撮影:</strong> Webカメラを使用して顔写真を撮影</li>
1194
- </ul>
1195
- </li>
1196
- <li>選択した方法で顔画像を入力します。</li>
1197
- <li>スライダーでリスクレベルを調整します。</li>
1198
- <li>「処理開始」ボタンをクリックして処理を実行します。</li>
1199
- </ol>
1200
- </div>
1201
-
1202
- <!-- 共通の注意事項 -->
1203
- <div class="help-section">
1204
- <h4><i class="fas fa-exclamation-triangle"></i> 注意事項</h4>
1205
- <ul>
1206
- <li>アップロードする画像は1200×1200ピクセ���以下に自動でリサイズされます。</li>
1207
- <li>処理には数秒から30秒ほどかかる場合があります。</li>
1208
- <li>処理が完了すると、処理結果が表示され、画像のダウンロードが可能になります。</li>
1209
- <li>ブラウザのプライバシー設定によっては、カメラの使用許可が必要な場合があります。</li>
1210
- </ul>
1211
- </div>
1212
- </div>
1213
- <div class="modal-footer">
1214
- <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button>
1215
- </div>
1216
- </div>
1217
- </div>
1218
- </div>
1219
-
1220
- <!-- 既存のスクリプト -->
1221
- <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
1222
- <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
1223
- <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
1224
- <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
1225
- <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
1226
- <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
1227
- <script>
1228
- // スライダーの初期化
1229
- $(function() {
1230
- $("#slider1, #slider2").slider({
1231
- range: "min",
1232
- value: 50,
1233
- min: 0,
1234
- max: 100,
1235
- slide: function(event, ui) {
1236
- const labelId = $(this).attr('id') === 'slider1' ? 'riskLevelLabel1' : 'riskLevelLabel2';
1237
- $("#" + labelId).text(ui.value);
1238
- }
1239
- });
1240
- });
1241
- let resizedImageBlob1 = null;
1242
- let resizedImageBlob2 = null;
1243
- let faceImageBlob = null;
1244
- function previewAndResizeImage(inputId, imageId) {
1245
- const fileInput = document.getElementById(inputId);
1246
- const uploadedImage = document.getElementById(imageId);
1247
-
1248
- if (fileInput.files && fileInput.files[0]) {
1249
- const reader = new FileReader();
1250
- reader.onload = function(e) {
1251
- const img = new Image();
1252
- img.onload = function() {
1253
- const maxWidth = 1200;
1254
- const maxHeight = 1200;
1255
- let width = img.width;
1256
- let height = img.height;
1257
- if (width > maxWidth || height > maxHeight) {
1258
- const ratio = Math.min(maxWidth / width, maxHeight / height);
1259
- width *= ratio;
1260
- height *= ratio;
1261
- }
1262
- const canvas = document.createElement('canvas');
1263
- canvas.width = width;
1264
- canvas.height = height;
1265
- const ctx = canvas.getContext('2d');
1266
- ctx.drawImage(img, 0, 0, width, height);
1267
- uploadedImage.src = canvas.toDataURL('image/jpeg');
1268
- uploadedImage.style.display = 'block';
1269
-
1270
- canvas.toBlob((blob) => {
1271
- if (inputId === 'uploadImage1') {
1272
- resizedImageBlob1 = blob;
1273
- } else {
1274
- resizedImageBlob2 = blob;
1275
- }
1276
- }, 'image/jpeg');
1277
- };
1278
- img.src = e.target.result;
1279
- };
1280
- reader.readAsDataURL(fileInput.files[0]);
1281
- }
1282
- }
1283
- function previewFaceImage() {
1284
- const fileInput = document.getElementById('uploadFace');
1285
- if (fileInput.files && fileInput.files[0]) {
1286
- const reader = new FileReader();
1287
- reader.onload = function(e) {
1288
- document.getElementById('facePreview').src = e.target.result;
1289
- document.getElementById('facePreview').style.display = 'block';
1290
- };
1291
- reader.readAsDataURL(fileInput.files[0]);
1292
- faceImageBlob = fileInput.files[0];
1293
- }
1294
- }
1295
- function toggleFaceInput() {
1296
- const faceOption = document.getElementById('faceOption').value;
1297
- document.getElementById('uploadFaceGroup').style.display = faceOption === 'upload' ? 'block' : 'none';
1298
- document.getElementById('cameraFaceGroup').style.display = faceOption === 'camera' ? 'block' : 'none';
1299
- if (faceOption === 'camera') {
1300
- startCamera();
1301
- } else {
1302
- stopCamera();
1303
- }
1304
- }
1305
- function startCamera() {
1306
- const video = document.getElementById('cameraStream');
1307
- navigator.mediaDevices.getUserMedia({ video: true })
1308
- .then(stream => video.srcObject = stream)
1309
- .catch(error => console.error('カメラの起動に失敗しました:', error));
1310
- }
1311
- function stopCamera() {
1312
- const video = document.getElementById('cameraStream');
1313
- const stream = video.srcObject;
1314
- if (stream) {
1315
- stream.getTracks().forEach(track => track.stop());
1316
- video.srcObject = null;
1317
- }
1318
- }
1319
- function captureFaceImage() {
1320
- const video = document.getElementById('cameraStream');
1321
- const canvas = document.getElementById('cameraCanvas');
1322
- const context = canvas.getContext('2d');
1323
- canvas.width = video.videoWidth;
1324
- canvas.height = video.videoHeight;
1325
- context.drawImage(video, 0, 0, canvas.width, canvas.height);
1326
- canvas.toBlob(blob => faceImageBlob = blob, 'image/jpeg');
1327
- stopCamera();
1328
- }
1329
- function processGeneralImage() {
1330
- if (!resizedImageBlob1) {
1331
- alert("画像を選択してください。");
1332
- return;
1333
- }
1334
- const processingType = document.getElementById('processingType').value;
1335
- const riskLevel = $("#slider1").slider("value");
1336
- showLoadingSpinner();
1337
- const formData = new FormData();
1338
- formData.append('image', resizedImageBlob1, 'resized_image.jpg');
1339
- formData.append('risk_level', riskLevel);
1340
- let apiEndpoint;
1341
- if (processingType === "opencv") {
1342
- apiEndpoint = "/create-mask-and-inpaint-opencv";
1343
- } else if (processingType === "simple_lama") {
1344
- apiEndpoint = "/create-mask-and-inpaint-simple-lama";
1345
- } else if (processingType === "stamp") {
1346
- apiEndpoint = "/create-mask-and-inpaint-stamp";
1347
- } else if (processingType === "mosaic") {
1348
- apiEndpoint = "/create-mask-and-inpaint-mosaic";
1349
- }
1350
- processImageRequest(formData, "https://rein0421-aidentify.hf.space" + apiEndpoint);
1351
- }
1352
- function processFaceImage() {
1353
- if (!resizedImageBlob2 || !faceImageBlob) {
1354
- alert("処理する画像と顔画像の両方を設定してください。");
1355
- return;
1356
- }
1357
- const riskLevel = $("#slider2").slider("value");
1358
- showLoadingSpinner();
1359
- const formData = new FormData();
1360
- formData.append('reference_image', faceImageBlob, 'reference_image.jpg');
1361
- formData.append('test_image', resizedImageBlob2, 'test_image.jpg');
1362
- formData.append('risk_level', riskLevel);
1363
- processImageRequest(formData, "https://rein0421-aidentify.hf.space/mosaic_faces");
1364
- }
1365
- function processImageRequest(formData, url) {
1366
- fetch(url, {
1367
- method: 'POST',
1368
- body: formData
1369
- })
1370
- .then(response => {
1371
- if (!response.ok) throw new Error("Network response was not ok");
1372
- return response.blob();
1373
- })
1374
- .then(blob => {
1375
- const objectURL = URL.createObjectURL(blob);
1376
- document.getElementById('processedImage').src = objectURL;
1377
- document.getElementById('downloadLink').href = objectURL;
1378
- document.getElementById('result').style.display = "block";
1379
- })
1380
- .catch(error => {
1381
- console.error("画像処理に失敗しました。", error);
1382
- alert("画像処理に失敗しました。");
1383
- })
1384
- .finally(() => {
1385
- hideLoadingSpinner();
1386
- });
1387
- }
1388
- function showLoadingSpinner() {
1389
- document.getElementById('loadingSpinner').style.display = 'block';
1390
- }
1391
- function hideLoadingSpinner() {
1392
- document.getElementById('loadingSpinner').style.display = 'none';
1393
- }
1394
- // タブ切り替え時の処理
1395
- $('.nav-tabs a').on('shown.bs.tab', function (e) {
1396
- // 結果表示をリセット
1397
- document.getElementById('result').style.display = 'none';
1398
- document.getElementById('processedImage').src = '';
1399
- document.getElementById('downloadLink').href = '#';
1400
- });
1401
- </script>
1402
- <script>
1403
-
1404
- </script>
1405
- </body>
1406
- </html>
1407
- """
1408
- return HTMLResponse(content=html_content)
1409
-
1410
 
 
11
 
12
 
13
 
14
+ from fastapi.templating import Jinja2Templates
15
  import os
16
  import supervision as sv
17
  from PIL import Image, ImageFilter
 
77
 
78
 
79
  HOME = "./"
80
+ templates = Jinja2Templates(directory="templates")
81
  dangerarray=[10,30,90,50,80,20,40,70,100,60]#ここに各クラスターの危険度を設定しておく
82
  #ここで認識する精度を上げたり下げたりできる
83
 
 
754
 
755
  # Save the input image
756
  input_path = save_image(image.file, "input.jpg")
757
+ print('point1,point2',point1,point2)
758
  # Create a mask image (using the new process_image function)
759
  mask_path = special_process_image_yolo(risk_level, input_path, point1, point2,thresholds=thresholds)
760
 
 
790
  input_path = save_image(image.file, f"./input_{timestamp}_{unique_id}.jpg")
791
  mask_path = special_process_image_yolo(risk_level, input_path, point1, point2,thresholds=thresholds)
792
  output_path = f"./output_simple_lama_{timestamp}_{unique_id}.jpg"
793
+ print('point1,point2',point1,point2)#消去したくない範囲のこと
794
  # OpenCVでインペイント
795
  inpaint_image_with_mask1(input_path, mask_path, output_path)
796
 
 
902
 
903
  @app.get("/", response_class=HTMLResponse)
904
  async def read_root():
905
+ return templates.TemplateResponse("index.html")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
906