Emeritus-21 commited on
Commit
7a4284c
·
verified ·
1 Parent(s): f9f0dcf

Update templates/browser-detect.html

Browse files
Files changed (1) hide show
  1. templates/browser-detect.html +74 -136
templates/browser-detect.html CHANGED
@@ -1,19 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
  <!--
3
  SignSpeak - A Communicator For Signers
4
 
5
  Copyright (C) 2025 Shantanu Khedkar
6
-
7
  This program is free software: you can redistribute it and/or modify
8
  it under the terms of the GNU General Public License as published by
9
  the Free Software Foundation, either version 3 of the License, or
10
  (at your option) any later version.
11
-
12
  This program is distributed in the hope that it will be useful,
13
  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
  GNU General Public License for more details.
16
-
17
  You should have received a copy of the GNU General Public License
18
  along with this program. If not, see <https://www.gnu.org/licenses/>.
19
  -->
@@ -274,28 +314,32 @@
274
  </div>
275
  </form>
276
  </section>
277
- <div id="home-" class="bottom-nav-btn center-btn">
278
- <img src="/static/logo_sil.svg" height="48" width="48px" alt="Home Icon" />
279
- </div>
280
- <div id="settings-" class="bottom-nav-btn">
281
- <svg xmlns="http://www.w3.org/2000/svg" height="28px" width="28px" viewBox="0 0 512 512">
282
- <path fill="#5d5d5d" d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z" />
283
- </svg>
284
- </div>
285
- <div id="digits-" class="bottom-nav-btn">
286
- <svg xmlns="http://www.w3.org/2000/svg" height="28px" width="28px" viewBox="0 0 512 512">
287
- <path fill="#5d5d5d" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM232 344V280H168c-13.3 0-24-10.7-24-24s10.7-24 24-24h64V168c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H280v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z" />
288
- </svg>
289
- <button id="goDigitsBtn">Go to Digits</button>
290
- </div>
291
  </div>
292
-
293
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  var word_list = []
295
  var speechSupported = true
296
  var prevSpeech = ""
297
  var prevSettings = ""
298
-
299
  logUI = document.getElementById("logUI")
300
  const speechSynthesis = window.speechSynthesis;
301
  const voiceSelect = document.getElementById('voice');
@@ -306,9 +350,7 @@ var word_list = []
306
  const pitchInput = document.getElementById('pitch');
307
  const rateValue = document.getElementById('rateValue');
308
  const pitchValue = document.getElementById('pitchValue');
309
-
310
  let voices = [];
311
-
312
  const languageNames = {
313
  'en': '- English ',
314
  'mra': '- Marathi',
@@ -404,7 +446,6 @@ var word_list = []
404
  'tt': 'Tatar',
405
  'ur': 'Urdu',
406
  'vi': 'Vietnamese'
407
-
408
  };
409
  function undo() {
410
  word_list.pop()
@@ -426,7 +467,6 @@ var word_list = []
426
  if (!response.ok) {
427
  throw new Error(`Response status: ${response.status}`);
428
  }
429
-
430
  var voicesList = await response.text();
431
  voicesList = voicesList.split('\n')
432
  voicesList.pop()
@@ -447,7 +487,6 @@ var word_list = []
447
  if (!response.ok) {
448
  throw new Error(`Response status: ${response.status}`);
449
  }
450
-
451
  var langsList = await response.text();
452
  langsList = langsList.split('\n')
453
  langsList.pop()
@@ -471,14 +510,12 @@ var word_list = []
471
  } catch (error) {
472
  console.error(error.message);
473
  }
474
-
475
  const optionNodes = Array.from(languageSelect.children);
476
  const comparator = new Intl.Collator('en'.slice(0, 2)).compare;
477
  optionNodes.sort((a, b) => comparator(a.textContent, b.textContent));
478
  optionNodes.forEach((option) => languageSelect.appendChild(option));
479
  languageSelect.children[0].selected = "selected"
480
  }
481
-
482
  function getBaseLanguageCode(lang) {
483
  return lang.split('-').slice(0, 2).join('-');
484
  }
@@ -488,11 +525,9 @@ var word_list = []
488
  function getModifierLanguageCode(lang) {
489
  return lang.split('-').slice(1, 3).join('-');
490
  }
491
-
492
  function updateLanguageList() {
493
  const uniqueLanguages = new Set(voices.map(voice => getBaseLanguageCode(voice.lang)));
494
  languageSelect.innerHTML = '';
495
-
496
  Object.keys(languageNames).forEach(lang => {
497
  if (uniqueLanguages.has(lang)) {
498
  const option = document.createElement('option');
@@ -501,7 +536,6 @@ var word_list = []
501
  languageSelect.appendChild(option);
502
  }
503
  });
504
-
505
  uniqueLanguages.forEach(lang => {
506
  if (!languageNames[lang]) {
507
  if (!languageNames[getBaseBaseLanguageCode(lang)]) {
@@ -518,18 +552,15 @@ var word_list = []
518
  }
519
  }
520
  });
521
-
522
  const optionNodes = Array.from(languageSelect.children);
523
  const comparator = new Intl.Collator('en'.slice(0, 2)).compare;
524
  optionNodes.sort((a, b) => comparator(a.textContent, b.textContent));
525
  optionNodes.forEach((option) => languageSelect.appendChild(option));
526
  languageSelect.children[0].selected = "selected"
527
  }
528
-
529
  function updateVoiceList() {
530
  const selectedLanguage = languageSelect.value;
531
  voiceSelect.innerHTML = '';
532
-
533
  voices.forEach((voice) => {
534
  if (getBaseLanguageCode(voice.lang) === selectedLanguage) {
535
  const option = document.createElement('option');
@@ -548,9 +579,7 @@ var word_list = []
548
  logUI.appendChild(span);
549
  logUI.appendChild(document.createElement('br')); // Add a line break
550
  }
551
-
552
  const originalFetch = window.fetch;
553
-
554
  // Override the fetch function
555
  window.fetch = async function (input, init) {
556
  // Convert input to URL if it's a Request object
@@ -559,31 +588,22 @@ var word_list = []
559
  if (url == 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.wasm') {
560
  //newUrl = 'http://127.0.0.1:8125/assets/static/vision_wasm_internal.wasm' //For Android
561
  newUrl = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.wasm' // For Web
562
-
563
  }
564
  console.log("This was FETCHED: ", newUrl)
565
  // Call the original fetch function with the new URL
566
  return originalFetch(newUrl, init);
567
  };
568
-
569
-
570
-
571
-
572
  if ('speechSynthesis' in window) {
573
  speechSynthesis.onvoiceschanged = () => {
574
  populateVoiceList();
575
  };
576
-
577
  languageSelect.addEventListener('change', updateVoiceList);
578
-
579
  rateInput.addEventListener('input', () => {
580
  rateValue.textContent = rateInput.value;
581
  });
582
-
583
  pitchInput.addEventListener('input', () => {
584
  pitchValue.textContent = pitchInput.value;
585
  });
586
-
587
  speakButton.addEventListener('click', () => {
588
  const utterance = new SpeechSynthesisUtterance(textInput.value);
589
  const selectedVoice = voices.find(voice => voice.name === voiceSelect.value);
@@ -592,18 +612,14 @@ var word_list = []
592
  utterance.pitch = pitchInput.value;
593
  speechSynthesis.speak(utterance);
594
  });
595
-
596
  populateVoiceList()
597
  // Create an utterance object ⣿
598
-
599
  } else {
600
  speechSupported = false;
601
  console.log('Text-to-speech not supported.');
602
-
603
  populateTTWLangs()
604
  populateTTWVoices()
605
  }
606
-
607
  function speak(toSpeak) {
608
  console.log("speech api support", speechSupported)
609
  console.log("condition: ", !speechSupported)
@@ -618,10 +634,8 @@ var word_list = []
618
  audioPlayer.src = 'http://127.0.0.1:8125/speech?t=' + encodeURIComponent(toSpeak) + currSettings
619
  console.log("Set src: ", audioPlayer.src)
620
  }
621
-
622
  audioPlayer.play() // Play the audio
623
  .then(() => {
624
-
625
  console.log('Audio is playing');
626
  })
627
  .catch(error => {
@@ -634,9 +648,6 @@ var word_list = []
634
  console.log("Text to speech is now not supported")
635
  }
636
  }
637
-
638
-
639
-
640
  function set_output_array(text) {
641
  console.log(text)
642
  word_list = text.split("");
@@ -646,14 +657,12 @@ var word_list = []
646
  word_list = [];
647
  textInput.value = ""
648
  }
649
-
650
  </script>
651
 
652
  <script type="module">
653
  document.getElementById("info-").addEventListener("click", switchPage.bind(null, "info-"));
654
  document.getElementById("home-").addEventListener("click", switchPage.bind(null, "home-"));
655
  document.getElementById("settings-").addEventListener("click", switchPage.bind(null, "settings-"));
656
-
657
  //import { HandLandmarker, FilesetResolver } from "http://127.0.0.1:8125/assets/static/tasks-vision@0.10.0" // For Android
658
  import { HandLandmarker, FilesetResolver } from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0"; // For Web
659
  let handLandmarker = undefined;
@@ -663,7 +672,6 @@ var word_list = []
663
  var time_since_letter = 0
664
  var last_letter_time = 0
665
  var maxPercentage = 1
666
-
667
  function inputFrequency() {
668
  return document.getElementById('duration').value
669
  }
@@ -672,11 +680,10 @@ var word_list = []
672
  }
673
  document.getElementById('durationValue').innerText = inputFrequency()
674
  document.getElementById('repeatValue').innerText = repeatBuffer()
675
- var is_first_run = 1
676
  const letter_list = ["A", "B", "", "D", "E", "F", "G", "", "I", "J", "K", "L", "M", "N", "O", "P", "", "R", "S", "T", "U", "V", "W", "", "Y", "Z", ""]
677
  const phrase_list = ["Ere-ọwurọ", "Bọkọ", "Bokọ???", "Ere-Alẹ", "Mo dọkpẹ", "Ẹrẹ"]
678
  // const phrase_list = [" Good-morning", " Hello", " How are you", "Good-evening", " Thanks", " You"]
679
- var index_list = letter_list
680
  // Before we can use HandLandmarker class we must wait for it to finish
681
  // loading. Machine Learning models can be large and take a moment to
682
  // get everything needed to run.
@@ -693,12 +700,10 @@ var word_list = []
693
  });
694
  };
695
  createHandLandmarker();
696
-
697
  //const MODEL_PATH = "http://127.0.0.1:8125/assets/static/model.tflite" // For Android
698
  //const WORD_MODEL = "http://127.0.0.1:8125/assets/static/word.tflite" // For Android
699
  const MODEL_PATH = "/exported" // For Web
700
  const WORD_MODEL = "/word" // For Web
701
-
702
  const letterDetector = tflite.loadTFLiteModel(MODEL_PATH);
703
  const wordDetector = tflite.loadTFLiteModel(WORD_MODEL);
704
  var objectDetector = letterDetector
@@ -768,23 +773,18 @@ var word_list = []
768
  enableWebcamButton.style = "display:none"
769
  document.getElementById("switch-camera").style.display = "block"
770
  document.getElementById("mode-switch").style.display = "flex"
771
-
772
  }
773
  // getUsermedia parameters.
774
  load_camera()
775
  }
776
-
777
  function switchPage(elem) {
778
  prevSpeech = ""
779
-
780
  var pH = document.getElementById("home-page")
781
  var pI = document.getElementById("info-page")
782
  var pS = document.getElementById("settings-page")
783
-
784
  pH.style.display = "none"
785
  pI.style.display = "none"
786
  pS.style.display = "none"
787
-
788
  document.getElementById(elem + "page").style.display = "block"
789
  if (elem != "home-") {
790
  webcamRunning = false
@@ -802,7 +802,6 @@ var word_list = []
802
  context.clearRect(0, 0, canvas.width, canvas.height);
803
  }
804
  }
805
-
806
  function load_camera() {
807
  try {
808
  var stream = video.srcObject;
@@ -818,7 +817,6 @@ var word_list = []
818
  } catch (error) {
819
  console.error(error.message);
820
  }
821
-
822
  const constraints = {
823
  video: {
824
  facingMode: video_facing_mode
@@ -855,18 +853,14 @@ var word_list = []
855
  }
856
  canvasCtx.save();
857
  canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
858
-
859
  if (is_first_run == 1) {
860
  var elem_rect = document.getElementById("output_canvas").getBoundingClientRect()
861
  console.log(elem_rect.height | 0);
862
  document.getElementById("canvas_wrapper").style.height = (elem_rect.height | 0).toString() + "px"
863
-
864
  is_first_run = 0
865
  }
866
-
867
  if (results.landmarks && results.handednesses[0]) {
868
  var current_time = Math.round(Date.now())
869
-
870
  if (results.handednesses[0][0].categoryName == "Left") {
871
  if (!Array.from(document.getElementById('modeSelector').classList).includes('right')) {
872
  annotateImage()
@@ -881,8 +875,6 @@ var word_list = []
881
  var current_result = "_"
882
  var previous_result = document.getElementById("predicted_result").innerText
883
  document.getElementById("predicted_result").innerText = current_result
884
-
885
-
886
  if (previous_result == current_result) {
887
  if (current_time - last_letter_time > getNaturalLength(" ")) {
888
  last_letter_time = current_time
@@ -901,7 +893,6 @@ var word_list = []
901
  else {
902
  canvasCtx.drawImage(video, 0, 0, canvasElement.width, (video.videoHeight / video.videoWidth) * canvasElement.width)
903
  if (30 > calculateCanvasBrightness(canvasElement)) {
904
-
905
  var current_result = "<"
906
  var previous_result = document.getElementById("predicted_result").innerText
907
  document.getElementById("predicted_result").innerText = current_result
@@ -920,11 +911,9 @@ var word_list = []
920
  }
921
  } else {
922
  last_letter_time = Math.round(Date.now())
923
-
924
  document.getElementById("predicted_result").style.width = String(0) + "%"
925
  }
926
  }
927
-
928
  canvasCtx.restore();
929
  // Keep predicting
930
  if (webcamRunning === true) {
@@ -932,7 +921,6 @@ var word_list = []
932
  }
933
  }
934
  function annotateImage(firstA = true) {
935
-
936
  //console.log(results.landmarks)
937
  if (results.landmarks[0]) {
938
  x_array = []
@@ -945,12 +933,10 @@ var word_list = []
945
  var min_y = Math.min(...y_array) * image_height
946
  var max_x = Math.max(...x_array) * image_width
947
  var max_y = Math.max(...y_array) * image_height
948
-
949
  var sect_height = max_y - (min_y)
950
  var sect_width = max_x - (min_x)
951
  var center_x = (min_x + max_x) / 2
952
  var center_y = (min_y + max_y) / 2
953
-
954
  var sect_diameter = 50
955
  if (sect_height > sect_width) {
956
  sect_diameter = sect_height
@@ -960,7 +946,6 @@ var word_list = []
960
  sect_diameter = sect_width
961
  // console.log("sect_width", sect_diameter)
962
  }
963
-
964
  sect_diameter = sect_diameter + 50
965
  var sect_radius = sect_diameter / 2
966
  var crop_top = center_y - sect_radius
@@ -987,15 +972,11 @@ var word_list = []
987
  canvasCtx.fillStyle = "#ffffff"
988
  canvasCtx.fill()
989
  }
990
-
991
  } else {
992
  canvasCtx.beginPath();
993
  canvasCtx.rect(crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top);
994
  canvasCtx.stroke();
995
  }
996
-
997
-
998
-
999
  }
1000
  /* for (const landmarks of results.multiHandLandmarks) {
1001
  drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {
@@ -1019,102 +1000,76 @@ var word_list = []
1019
  drawConnection(hand[4], hand[3], 'rgb(0, 0, 255)', 5); // 4-3 (was red)
1020
  drawConnection(hand[3], hand[2], 'rgb(0, 0, 255)', 5); // 3-2 (was red)
1021
  drawConnection(hand[2], hand[1], 'rgb(0, 0, 255)', 5); // 2-1 (was red)
1022
-
1023
  // Index connections
1024
  drawConnection(hand[8], hand[7], 'rgb(0, 255, 0)', 5); // 8-7
1025
  drawConnection(hand[7], hand[6], 'rgb(0, 255, 0)', 5); // 7-6
1026
  drawConnection(hand[6], hand[5], 'rgb(0, 255, 0)', 5); // 6-5
1027
-
1028
  // Middle connections
1029
  drawConnection(hand[12], hand[11], 'rgb(255, 0, 0)', 5); // 12-11 (was blue)
1030
  drawConnection(hand[11], hand[10], 'rgb(255, 0, 0)', 5); // 11-10 (was blue)
1031
  drawConnection(hand[10], hand[9], 'rgb(255, 0, 0)', 5); // 10-9 (was blue)
1032
-
1033
  // Ring connections
1034
  drawConnection(hand[16], hand[15], 'rgb(0, 255, 255)', 5); // 16-15 (was yellow)
1035
  drawConnection(hand[15], hand[14], 'rgb(0, 255, 255)', 5); // 15-14 (was yellow)
1036
  drawConnection(hand[14], hand[13], 'rgb(0, 255, 255)', 5); // 14-13 (was yellow)
1037
-
1038
  // Pinky connections
1039
  drawConnection(hand[20], hand[19], 'rgb(255, 0, 255)', 5); // 20-19
1040
  drawConnection(hand[19], hand[18], 'rgb(255, 0, 255)', 5); // 19-18
1041
  drawConnection(hand[18], hand[17], 'rgb(255, 0, 255)', 5); // 18-17
1042
-
1043
  drawConnection(hand[0], hand[1], 'rgb(200, 200, 200)', 5); // 0-1
1044
  drawConnection(hand[0], hand[5], 'rgb(200, 200, 200)', 5); // 0-5
1045
  drawConnection(hand[0], hand[17], 'rgb(200, 200, 200)', 5); // 0-17
1046
  drawConnection(hand[5], hand[9], 'rgb(200, 200, 200)', 5); // 5-9
1047
  drawConnection(hand[9], hand[13], 'rgb(200, 200, 200)', 5); // 9-13
1048
  drawConnection(hand[13], hand[17], 'rgb(200, 200, 200)', 5); // 13-17
1049
-
1050
  // Thumb
1051
  drawLandmarks(canvasCtx, hand[2], '#ffe5b4'); // Thumb tip (2)
1052
  drawLandmarks(canvasCtx, hand[3], '#ffe5b4'); // Thumb base (3)
1053
  drawLandmarks(canvasCtx, hand[4], '#ffe5b4'); // Thumb base (4)
1054
-
1055
  // Index
1056
  drawLandmarks(canvasCtx, hand[6], '#804080'); // Index tip (6)
1057
  drawLandmarks(canvasCtx, hand[7], '#804080'); // Index base (7)
1058
  drawLandmarks(canvasCtx, hand[8], '#804080'); // Index base (8)
1059
-
1060
  // Middle
1061
  drawLandmarks(canvasCtx, hand[10], '#ffcc00'); // Middle tip (10)
1062
  drawLandmarks(canvasCtx, hand[11], '#ffcc00'); // Middle base (11)
1063
  drawLandmarks(canvasCtx, hand[12], '#ffcc00'); // Middle base (12)
1064
-
1065
  // Ring
1066
  drawLandmarks(canvasCtx, hand[14], '#30ff30'); // Ring tip (14)
1067
  drawLandmarks(canvasCtx, hand[15], '#30ff30'); // Ring base (15)
1068
  drawLandmarks(canvasCtx, hand[16], '#30ff30'); // Ring base (16)
1069
-
1070
  // Pinky
1071
  drawLandmarks(canvasCtx, hand[18], '#1565c0'); // Pinky tip (18)
1072
  drawLandmarks(canvasCtx, hand[19], '#1565c0'); // Pinky base (19)
1073
  drawLandmarks(canvasCtx, hand[20], '#1565c0'); // Pinky base (20)
1074
-
1075
  drawLandmarks(canvasCtx, hand[0], '#ff3030'); // Wrist (0)
1076
-
1077
  drawLandmarks(canvasCtx, hand[1], '#ff3030'); // Palm base (1)
1078
-
1079
  drawLandmarks(canvasCtx, hand[5], '#ff3030'); // Index palm (5)
1080
-
1081
  drawLandmarks(canvasCtx, hand[9], '#ff3030'); // Middle palm (9)
1082
-
1083
  drawLandmarks(canvasCtx, hand[13], '#ff3030'); // Ring palm (13)
1084
-
1085
  drawLandmarks(canvasCtx, hand[17], '#ff3030'); // Pinky palm (17)
1086
-
1087
  // Crop Canvas
1088
  if (firstA == true) {
1089
  cropCanvas(canvasElement, crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top)
1090
  }
1091
  }
1092
  // Add more drawing calls for each landmark collection as needed
1093
-
1094
-
1095
-
1096
-
1097
  //# sourceURL=pen.js
1098
  }
1099
-
1100
-
1101
  function iterate(x, y) {
1102
  x_array.push(x.x)
1103
  y_array.push(x.y)
1104
  }
1105
-
1106
  const cropCanvas = (sourceCanvas, left, top, width, height) => {
1107
  let destCanvas = document.createElement('canvas');
1108
  destCanvas.width = 224;
1109
  var cropAspectRatio = width / height;
1110
-
1111
  destCanvas.height = 224 / cropAspectRatio
1112
  destCanvas.getContext("2d").drawImage(
1113
  sourceCanvas,
1114
  left, top, width, height, // source rect with content to crop
1115
  0, 0, 224, destCanvas.height); // newCanvas, same size as source
1116
  var predictionInput = tf.browser.fromPixels(destCanvas.getContext("2d").getImageData(0, 0, 224, 224))
1117
-
1118
  predict(tf.expandDims(predictionInput, 0));
1119
  }
1120
  function getNaturalLength(letter) {
@@ -1142,28 +1097,19 @@ var word_list = []
1142
  function mapLogitsToPercentage(logits) {
1143
  // Apply ceiling of 260
1144
  const cappedLogits = logits.map(value => Math.min(value, 260));
1145
-
1146
  // Map the values from 0-260 to 0-100
1147
  const mappedPercentages = cappedLogits.map(value => (value / 260) * 100);
1148
-
1149
  return mappedPercentages;
1150
  }
1151
-
1152
-
1153
  async function predict(inputTensor) {
1154
-
1155
  console.log(index_list[0])
1156
  objectDetector.then(function (res) {
1157
-
1158
  var prediction = res.predict(inputTensor);
1159
-
1160
  var outputArray = prediction.dataSync(); // Get the output as an array
1161
  var percentages = mapLogitsToPercentage(outputArray)
1162
  maxPercentage = Math.max(...percentages)
1163
  document.getElementById('predicted_result').style.opacity = (maxPercentage+30)+'%'
1164
-
1165
  var predictedClass = percentages.indexOf(Math.max(...percentages)); // Get the index
1166
-
1167
  var current_result = index_list[predictedClass]
1168
  global_cres = current_result
1169
  var previous_result = document.getElementById("predicted_result").innerText
@@ -1174,7 +1120,6 @@ var word_list = []
1174
  }
1175
  console.log("p:", previous_result, "c:", current_result)
1176
  if (previous_result == current_result) {
1177
-
1178
  if (current_time - last_letter_time > getNaturalLength(current_result)) {
1179
  last_letter_time = current_time
1180
  word_list.push(current_result)
@@ -1190,25 +1135,18 @@ var word_list = []
1190
  }, function (err) {
1191
  console.log(err);
1192
  });
1193
-
1194
  }
1195
-
1196
  function drawLandmarks(canvasCtx, landmarks, color) {
1197
  var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
1198
  var image_width = canvasElement.width
1199
-
1200
  canvasCtx.fillStyle = `rgb(${landmarks.z},${landmarks.z},${landmarks.z})`;
1201
  canvasCtx.beginPath();
1202
  canvasCtx.arc(landmarks.x * image_width, landmarks.y * image_height, 8, 0, 2 * Math.PI);
1203
  canvasCtx.fill();
1204
-
1205
  }
1206
-
1207
  function drawConnection(startNode, endNode, strokeColor, strokeWidth) {
1208
-
1209
  var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
1210
  var image_width = canvasElement.width
1211
-
1212
  canvasCtx.strokeStyle = strokeColor;
1213
  canvasCtx.lineWidth = strokeWidth - 1;
1214
  canvasCtx.beginPath();
@@ -1218,29 +1156,23 @@ var word_list = []
1218
  }
1219
  function calculateCanvasBrightness(canvas) {
1220
  const context = canvas.getContext('2d', { willReadFrequently: true });
1221
-
1222
  // Get the image data from the canvas
1223
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
1224
  const data = imageData.data;
1225
-
1226
  let totalBrightness = 0;
1227
  let pixelCount = 0;
1228
-
1229
  // Loop through each pixel
1230
  for (let i = 0; i < data.length; i += 4) {
1231
  const r = data[i]; // Red
1232
  const g = data[i + 1]; // Green
1233
  const b = data[i + 2]; // Blue
1234
-
1235
  // Calculate brightness for this pixel
1236
  const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
1237
  totalBrightness += brightness;
1238
  pixelCount++;
1239
  }
1240
-
1241
  // Calculate average brightness
1242
  const averageBrightness = totalBrightness / pixelCount;
1243
-
1244
  return averageBrightness;
1245
  }
1246
  function triggerPulse() {
@@ -1248,13 +1180,19 @@ var word_list = []
1248
  /* Apply after working on pulse css more
1249
  const resultWrapper = document.querySelector('.wrapper_result');
1250
  resultWrapper.classList.add('pulse');
1251
-
1252
  // Remove the class after the animation ends to allow it to be triggered again
1253
  resultWrapper.addEventListener('animationend', () => {
1254
- resultWrapper.classList.remove('pulse');
1255
  }, { once: true });*/
1256
  }
1257
-
 
 
 
 
 
1258
 
1259
  </body>
 
1260
  </html>
 
 
1
+ Hugging Face's logo
2
+ Hugging Face
3
+ Models
4
+ Datasets
5
+ Spaces
6
+ Community
7
+ Docs
8
+ Pricing
9
+
10
+
11
+
12
+ Spaces:
13
+
14
+
15
+ HuggingFace-SK
16
+ /
17
+ Sign-Language-Interpreter
18
+
19
+
20
+ like
21
+ 3
22
+ App
23
+ Files
24
+ Community
25
+ Sign-Language-Interpreter
26
+ /
27
+ templates
28
+ /
29
+ browser-detect.html
30
+
31
+ HuggingFace-SK
32
+ update version
33
+ 0aacbf7
34
+ 6 months ago
35
+ raw
36
+
37
+ Copy download link
38
+ history
39
+ blame
40
+ contribute
41
+ delete
42
+
43
+ 60.6 kB
44
  <!DOCTYPE html>
45
  <!--
46
  SignSpeak - A Communicator For Signers
47
 
48
  Copyright (C) 2025 Shantanu Khedkar
 
49
  This program is free software: you can redistribute it and/or modify
50
  it under the terms of the GNU General Public License as published by
51
  the Free Software Foundation, either version 3 of the License, or
52
  (at your option) any later version.
 
53
  This program is distributed in the hope that it will be useful,
54
  but WITHOUT ANY WARRANTY; without even the implied warranty of
55
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56
  GNU General Public License for more details.
 
57
  You should have received a copy of the GNU General Public License
58
  along with this program. If not, see <https://www.gnu.org/licenses/>.
59
  -->
 
314
  </div>
315
  </form>
316
  </section>
317
+ <div class="bottom-nav">
318
+ <div id="info-" class="bottom-nav-btn">
319
+ <svg xmlns="http://www.w3.org/2000/svg" height="28px" width="28px" viewBox="0 0 512 512">
320
+ <path fill="#5d5d5d" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z" />
321
+ </svg>
 
 
 
 
 
 
 
 
 
322
  </div>
323
+ <div id="home-" class="bottom-nav-btn center-btn">
324
+ <img src="/static/logo_sil.svg" height="48" width="48px" alt="Home Icon" />
325
+ </div>
326
+ <div id="settings-" class="bottom-nav-btn">
327
+ <svg xmlns="http://www.w3.org/2000/svg" height="28px" width="28px" viewBox="0 0 512 512">
328
+ <path fill="#5d5d5d" d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z" />
329
+ </svg>
330
+ </div>
331
+ <div id="digits-" class="bottom-nav-btn">
332
+ <svg xmlns="http://www.w3.org/2000/svg" height="28px" width="28px" viewBox="0 0 512 512">
333
+ <path fill="#5d5d5d" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM232 344V280H168c-13.3 0-24-10.7-24-24s10.7-24 24-24h64V168c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H280v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z" />
334
+ </svg>
335
+ <button id="goDigitsBtn">Go to Digits</button>
336
+ </div>
337
+ </div>
338
+ <script>
339
  var word_list = []
340
  var speechSupported = true
341
  var prevSpeech = ""
342
  var prevSettings = ""
 
343
  logUI = document.getElementById("logUI")
344
  const speechSynthesis = window.speechSynthesis;
345
  const voiceSelect = document.getElementById('voice');
 
350
  const pitchInput = document.getElementById('pitch');
351
  const rateValue = document.getElementById('rateValue');
352
  const pitchValue = document.getElementById('pitchValue');
 
353
  let voices = [];
 
354
  const languageNames = {
355
  'en': '- English ',
356
  'mra': '- Marathi',
 
446
  'tt': 'Tatar',
447
  'ur': 'Urdu',
448
  'vi': 'Vietnamese'
 
449
  };
450
  function undo() {
451
  word_list.pop()
 
467
  if (!response.ok) {
468
  throw new Error(`Response status: ${response.status}`);
469
  }
 
470
  var voicesList = await response.text();
471
  voicesList = voicesList.split('\n')
472
  voicesList.pop()
 
487
  if (!response.ok) {
488
  throw new Error(`Response status: ${response.status}`);
489
  }
 
490
  var langsList = await response.text();
491
  langsList = langsList.split('\n')
492
  langsList.pop()
 
510
  } catch (error) {
511
  console.error(error.message);
512
  }
 
513
  const optionNodes = Array.from(languageSelect.children);
514
  const comparator = new Intl.Collator('en'.slice(0, 2)).compare;
515
  optionNodes.sort((a, b) => comparator(a.textContent, b.textContent));
516
  optionNodes.forEach((option) => languageSelect.appendChild(option));
517
  languageSelect.children[0].selected = "selected"
518
  }
 
519
  function getBaseLanguageCode(lang) {
520
  return lang.split('-').slice(0, 2).join('-');
521
  }
 
525
  function getModifierLanguageCode(lang) {
526
  return lang.split('-').slice(1, 3).join('-');
527
  }
 
528
  function updateLanguageList() {
529
  const uniqueLanguages = new Set(voices.map(voice => getBaseLanguageCode(voice.lang)));
530
  languageSelect.innerHTML = '';
 
531
  Object.keys(languageNames).forEach(lang => {
532
  if (uniqueLanguages.has(lang)) {
533
  const option = document.createElement('option');
 
536
  languageSelect.appendChild(option);
537
  }
538
  });
 
539
  uniqueLanguages.forEach(lang => {
540
  if (!languageNames[lang]) {
541
  if (!languageNames[getBaseBaseLanguageCode(lang)]) {
 
552
  }
553
  }
554
  });
 
555
  const optionNodes = Array.from(languageSelect.children);
556
  const comparator = new Intl.Collator('en'.slice(0, 2)).compare;
557
  optionNodes.sort((a, b) => comparator(a.textContent, b.textContent));
558
  optionNodes.forEach((option) => languageSelect.appendChild(option));
559
  languageSelect.children[0].selected = "selected"
560
  }
 
561
  function updateVoiceList() {
562
  const selectedLanguage = languageSelect.value;
563
  voiceSelect.innerHTML = '';
 
564
  voices.forEach((voice) => {
565
  if (getBaseLanguageCode(voice.lang) === selectedLanguage) {
566
  const option = document.createElement('option');
 
579
  logUI.appendChild(span);
580
  logUI.appendChild(document.createElement('br')); // Add a line break
581
  }
 
582
  const originalFetch = window.fetch;
 
583
  // Override the fetch function
584
  window.fetch = async function (input, init) {
585
  // Convert input to URL if it's a Request object
 
588
  if (url == 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.wasm') {
589
  //newUrl = 'http://127.0.0.1:8125/assets/static/vision_wasm_internal.wasm' //For Android
590
  newUrl = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.wasm' // For Web
 
591
  }
592
  console.log("This was FETCHED: ", newUrl)
593
  // Call the original fetch function with the new URL
594
  return originalFetch(newUrl, init);
595
  };
 
 
 
 
596
  if ('speechSynthesis' in window) {
597
  speechSynthesis.onvoiceschanged = () => {
598
  populateVoiceList();
599
  };
 
600
  languageSelect.addEventListener('change', updateVoiceList);
 
601
  rateInput.addEventListener('input', () => {
602
  rateValue.textContent = rateInput.value;
603
  });
 
604
  pitchInput.addEventListener('input', () => {
605
  pitchValue.textContent = pitchInput.value;
606
  });
 
607
  speakButton.addEventListener('click', () => {
608
  const utterance = new SpeechSynthesisUtterance(textInput.value);
609
  const selectedVoice = voices.find(voice => voice.name === voiceSelect.value);
 
612
  utterance.pitch = pitchInput.value;
613
  speechSynthesis.speak(utterance);
614
  });
 
615
  populateVoiceList()
616
  // Create an utterance object ⣿
 
617
  } else {
618
  speechSupported = false;
619
  console.log('Text-to-speech not supported.');
 
620
  populateTTWLangs()
621
  populateTTWVoices()
622
  }
 
623
  function speak(toSpeak) {
624
  console.log("speech api support", speechSupported)
625
  console.log("condition: ", !speechSupported)
 
634
  audioPlayer.src = 'http://127.0.0.1:8125/speech?t=' + encodeURIComponent(toSpeak) + currSettings
635
  console.log("Set src: ", audioPlayer.src)
636
  }
 
637
  audioPlayer.play() // Play the audio
638
  .then(() => {
 
639
  console.log('Audio is playing');
640
  })
641
  .catch(error => {
 
648
  console.log("Text to speech is now not supported")
649
  }
650
  }
 
 
 
651
  function set_output_array(text) {
652
  console.log(text)
653
  word_list = text.split("");
 
657
  word_list = [];
658
  textInput.value = ""
659
  }
 
660
  </script>
661
 
662
  <script type="module">
663
  document.getElementById("info-").addEventListener("click", switchPage.bind(null, "info-"));
664
  document.getElementById("home-").addEventListener("click", switchPage.bind(null, "home-"));
665
  document.getElementById("settings-").addEventListener("click", switchPage.bind(null, "settings-"));
 
666
  //import { HandLandmarker, FilesetResolver } from "http://127.0.0.1:8125/assets/static/tasks-vision@0.10.0" // For Android
667
  import { HandLandmarker, FilesetResolver } from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0"; // For Web
668
  let handLandmarker = undefined;
 
672
  var time_since_letter = 0
673
  var last_letter_time = 0
674
  var maxPercentage = 1
 
675
  function inputFrequency() {
676
  return document.getElementById('duration').value
677
  }
 
680
  }
681
  document.getElementById('durationValue').innerText = inputFrequency()
682
  document.getElementById('repeatValue').innerText = repeatBuffer()
683
+ var is_first_run = 1
684
  const letter_list = ["A", "B", "", "D", "E", "F", "G", "", "I", "J", "K", "L", "M", "N", "O", "P", "", "R", "S", "T", "U", "V", "W", "", "Y", "Z", ""]
685
  const phrase_list = ["Ere-ọwurọ", "Bọkọ", "Bokọ???", "Ere-Alẹ", "Mo dọkpẹ", "Ẹrẹ"]
686
  // const phrase_list = [" Good-morning", " Hello", " How are you", "Good-evening", " Thanks", " You"]
 
687
  // Before we can use HandLandmarker class we must wait for it to finish
688
  // loading. Machine Learning models can be large and take a moment to
689
  // get everything needed to run.
 
700
  });
701
  };
702
  createHandLandmarker();
 
703
  //const MODEL_PATH = "http://127.0.0.1:8125/assets/static/model.tflite" // For Android
704
  //const WORD_MODEL = "http://127.0.0.1:8125/assets/static/word.tflite" // For Android
705
  const MODEL_PATH = "/exported" // For Web
706
  const WORD_MODEL = "/word" // For Web
 
707
  const letterDetector = tflite.loadTFLiteModel(MODEL_PATH);
708
  const wordDetector = tflite.loadTFLiteModel(WORD_MODEL);
709
  var objectDetector = letterDetector
 
773
  enableWebcamButton.style = "display:none"
774
  document.getElementById("switch-camera").style.display = "block"
775
  document.getElementById("mode-switch").style.display = "flex"
 
776
  }
777
  // getUsermedia parameters.
778
  load_camera()
779
  }
 
780
  function switchPage(elem) {
781
  prevSpeech = ""
 
782
  var pH = document.getElementById("home-page")
783
  var pI = document.getElementById("info-page")
784
  var pS = document.getElementById("settings-page")
 
785
  pH.style.display = "none"
786
  pI.style.display = "none"
787
  pS.style.display = "none"
 
788
  document.getElementById(elem + "page").style.display = "block"
789
  if (elem != "home-") {
790
  webcamRunning = false
 
802
  context.clearRect(0, 0, canvas.width, canvas.height);
803
  }
804
  }
 
805
  function load_camera() {
806
  try {
807
  var stream = video.srcObject;
 
817
  } catch (error) {
818
  console.error(error.message);
819
  }
 
820
  const constraints = {
821
  video: {
822
  facingMode: video_facing_mode
 
853
  }
854
  canvasCtx.save();
855
  canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
 
856
  if (is_first_run == 1) {
857
  var elem_rect = document.getElementById("output_canvas").getBoundingClientRect()
858
  console.log(elem_rect.height | 0);
859
  document.getElementById("canvas_wrapper").style.height = (elem_rect.height | 0).toString() + "px"
 
860
  is_first_run = 0
861
  }
 
862
  if (results.landmarks && results.handednesses[0]) {
863
  var current_time = Math.round(Date.now())
 
864
  if (results.handednesses[0][0].categoryName == "Left") {
865
  if (!Array.from(document.getElementById('modeSelector').classList).includes('right')) {
866
  annotateImage()
 
875
  var current_result = "_"
876
  var previous_result = document.getElementById("predicted_result").innerText
877
  document.getElementById("predicted_result").innerText = current_result
 
 
878
  if (previous_result == current_result) {
879
  if (current_time - last_letter_time > getNaturalLength(" ")) {
880
  last_letter_time = current_time
 
893
  else {
894
  canvasCtx.drawImage(video, 0, 0, canvasElement.width, (video.videoHeight / video.videoWidth) * canvasElement.width)
895
  if (30 > calculateCanvasBrightness(canvasElement)) {
 
896
  var current_result = "<"
897
  var previous_result = document.getElementById("predicted_result").innerText
898
  document.getElementById("predicted_result").innerText = current_result
 
911
  }
912
  } else {
913
  last_letter_time = Math.round(Date.now())
 
914
  document.getElementById("predicted_result").style.width = String(0) + "%"
915
  }
916
  }
 
917
  canvasCtx.restore();
918
  // Keep predicting
919
  if (webcamRunning === true) {
 
921
  }
922
  }
923
  function annotateImage(firstA = true) {
 
924
  //console.log(results.landmarks)
925
  if (results.landmarks[0]) {
926
  x_array = []
 
933
  var min_y = Math.min(...y_array) * image_height
934
  var max_x = Math.max(...x_array) * image_width
935
  var max_y = Math.max(...y_array) * image_height
 
936
  var sect_height = max_y - (min_y)
937
  var sect_width = max_x - (min_x)
938
  var center_x = (min_x + max_x) / 2
939
  var center_y = (min_y + max_y) / 2
 
940
  var sect_diameter = 50
941
  if (sect_height > sect_width) {
942
  sect_diameter = sect_height
 
946
  sect_diameter = sect_width
947
  // console.log("sect_width", sect_diameter)
948
  }
 
949
  sect_diameter = sect_diameter + 50
950
  var sect_radius = sect_diameter / 2
951
  var crop_top = center_y - sect_radius
 
972
  canvasCtx.fillStyle = "#ffffff"
973
  canvasCtx.fill()
974
  }
 
975
  } else {
976
  canvasCtx.beginPath();
977
  canvasCtx.rect(crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top);
978
  canvasCtx.stroke();
979
  }
 
 
 
980
  }
981
  /* for (const landmarks of results.multiHandLandmarks) {
982
  drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {
 
1000
  drawConnection(hand[4], hand[3], 'rgb(0, 0, 255)', 5); // 4-3 (was red)
1001
  drawConnection(hand[3], hand[2], 'rgb(0, 0, 255)', 5); // 3-2 (was red)
1002
  drawConnection(hand[2], hand[1], 'rgb(0, 0, 255)', 5); // 2-1 (was red)
 
1003
  // Index connections
1004
  drawConnection(hand[8], hand[7], 'rgb(0, 255, 0)', 5); // 8-7
1005
  drawConnection(hand[7], hand[6], 'rgb(0, 255, 0)', 5); // 7-6
1006
  drawConnection(hand[6], hand[5], 'rgb(0, 255, 0)', 5); // 6-5
 
1007
  // Middle connections
1008
  drawConnection(hand[12], hand[11], 'rgb(255, 0, 0)', 5); // 12-11 (was blue)
1009
  drawConnection(hand[11], hand[10], 'rgb(255, 0, 0)', 5); // 11-10 (was blue)
1010
  drawConnection(hand[10], hand[9], 'rgb(255, 0, 0)', 5); // 10-9 (was blue)
 
1011
  // Ring connections
1012
  drawConnection(hand[16], hand[15], 'rgb(0, 255, 255)', 5); // 16-15 (was yellow)
1013
  drawConnection(hand[15], hand[14], 'rgb(0, 255, 255)', 5); // 15-14 (was yellow)
1014
  drawConnection(hand[14], hand[13], 'rgb(0, 255, 255)', 5); // 14-13 (was yellow)
 
1015
  // Pinky connections
1016
  drawConnection(hand[20], hand[19], 'rgb(255, 0, 255)', 5); // 20-19
1017
  drawConnection(hand[19], hand[18], 'rgb(255, 0, 255)', 5); // 19-18
1018
  drawConnection(hand[18], hand[17], 'rgb(255, 0, 255)', 5); // 18-17
 
1019
  drawConnection(hand[0], hand[1], 'rgb(200, 200, 200)', 5); // 0-1
1020
  drawConnection(hand[0], hand[5], 'rgb(200, 200, 200)', 5); // 0-5
1021
  drawConnection(hand[0], hand[17], 'rgb(200, 200, 200)', 5); // 0-17
1022
  drawConnection(hand[5], hand[9], 'rgb(200, 200, 200)', 5); // 5-9
1023
  drawConnection(hand[9], hand[13], 'rgb(200, 200, 200)', 5); // 9-13
1024
  drawConnection(hand[13], hand[17], 'rgb(200, 200, 200)', 5); // 13-17
 
1025
  // Thumb
1026
  drawLandmarks(canvasCtx, hand[2], '#ffe5b4'); // Thumb tip (2)
1027
  drawLandmarks(canvasCtx, hand[3], '#ffe5b4'); // Thumb base (3)
1028
  drawLandmarks(canvasCtx, hand[4], '#ffe5b4'); // Thumb base (4)
 
1029
  // Index
1030
  drawLandmarks(canvasCtx, hand[6], '#804080'); // Index tip (6)
1031
  drawLandmarks(canvasCtx, hand[7], '#804080'); // Index base (7)
1032
  drawLandmarks(canvasCtx, hand[8], '#804080'); // Index base (8)
 
1033
  // Middle
1034
  drawLandmarks(canvasCtx, hand[10], '#ffcc00'); // Middle tip (10)
1035
  drawLandmarks(canvasCtx, hand[11], '#ffcc00'); // Middle base (11)
1036
  drawLandmarks(canvasCtx, hand[12], '#ffcc00'); // Middle base (12)
 
1037
  // Ring
1038
  drawLandmarks(canvasCtx, hand[14], '#30ff30'); // Ring tip (14)
1039
  drawLandmarks(canvasCtx, hand[15], '#30ff30'); // Ring base (15)
1040
  drawLandmarks(canvasCtx, hand[16], '#30ff30'); // Ring base (16)
 
1041
  // Pinky
1042
  drawLandmarks(canvasCtx, hand[18], '#1565c0'); // Pinky tip (18)
1043
  drawLandmarks(canvasCtx, hand[19], '#1565c0'); // Pinky base (19)
1044
  drawLandmarks(canvasCtx, hand[20], '#1565c0'); // Pinky base (20)
 
1045
  drawLandmarks(canvasCtx, hand[0], '#ff3030'); // Wrist (0)
 
1046
  drawLandmarks(canvasCtx, hand[1], '#ff3030'); // Palm base (1)
 
1047
  drawLandmarks(canvasCtx, hand[5], '#ff3030'); // Index palm (5)
 
1048
  drawLandmarks(canvasCtx, hand[9], '#ff3030'); // Middle palm (9)
 
1049
  drawLandmarks(canvasCtx, hand[13], '#ff3030'); // Ring palm (13)
 
1050
  drawLandmarks(canvasCtx, hand[17], '#ff3030'); // Pinky palm (17)
 
1051
  // Crop Canvas
1052
  if (firstA == true) {
1053
  cropCanvas(canvasElement, crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top)
1054
  }
1055
  }
1056
  // Add more drawing calls for each landmark collection as needed
 
 
 
 
1057
  //# sourceURL=pen.js
1058
  }
 
 
1059
  function iterate(x, y) {
1060
  x_array.push(x.x)
1061
  y_array.push(x.y)
1062
  }
 
1063
  const cropCanvas = (sourceCanvas, left, top, width, height) => {
1064
  let destCanvas = document.createElement('canvas');
1065
  destCanvas.width = 224;
1066
  var cropAspectRatio = width / height;
 
1067
  destCanvas.height = 224 / cropAspectRatio
1068
  destCanvas.getContext("2d").drawImage(
1069
  sourceCanvas,
1070
  left, top, width, height, // source rect with content to crop
1071
  0, 0, 224, destCanvas.height); // newCanvas, same size as source
1072
  var predictionInput = tf.browser.fromPixels(destCanvas.getContext("2d").getImageData(0, 0, 224, 224))
 
1073
  predict(tf.expandDims(predictionInput, 0));
1074
  }
1075
  function getNaturalLength(letter) {
 
1097
  function mapLogitsToPercentage(logits) {
1098
  // Apply ceiling of 260
1099
  const cappedLogits = logits.map(value => Math.min(value, 260));
 
1100
  // Map the values from 0-260 to 0-100
1101
  const mappedPercentages = cappedLogits.map(value => (value / 260) * 100);
 
1102
  return mappedPercentages;
1103
  }
 
 
1104
  async function predict(inputTensor) {
 
1105
  console.log(index_list[0])
1106
  objectDetector.then(function (res) {
 
1107
  var prediction = res.predict(inputTensor);
 
1108
  var outputArray = prediction.dataSync(); // Get the output as an array
1109
  var percentages = mapLogitsToPercentage(outputArray)
1110
  maxPercentage = Math.max(...percentages)
1111
  document.getElementById('predicted_result').style.opacity = (maxPercentage+30)+'%'
 
1112
  var predictedClass = percentages.indexOf(Math.max(...percentages)); // Get the index
 
1113
  var current_result = index_list[predictedClass]
1114
  global_cres = current_result
1115
  var previous_result = document.getElementById("predicted_result").innerText
 
1120
  }
1121
  console.log("p:", previous_result, "c:", current_result)
1122
  if (previous_result == current_result) {
 
1123
  if (current_time - last_letter_time > getNaturalLength(current_result)) {
1124
  last_letter_time = current_time
1125
  word_list.push(current_result)
 
1135
  }, function (err) {
1136
  console.log(err);
1137
  });
 
1138
  }
 
1139
  function drawLandmarks(canvasCtx, landmarks, color) {
1140
  var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
1141
  var image_width = canvasElement.width
 
1142
  canvasCtx.fillStyle = `rgb(${landmarks.z},${landmarks.z},${landmarks.z})`;
1143
  canvasCtx.beginPath();
1144
  canvasCtx.arc(landmarks.x * image_width, landmarks.y * image_height, 8, 0, 2 * Math.PI);
1145
  canvasCtx.fill();
 
1146
  }
 
1147
  function drawConnection(startNode, endNode, strokeColor, strokeWidth) {
 
1148
  var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
1149
  var image_width = canvasElement.width
 
1150
  canvasCtx.strokeStyle = strokeColor;
1151
  canvasCtx.lineWidth = strokeWidth - 1;
1152
  canvasCtx.beginPath();
 
1156
  }
1157
  function calculateCanvasBrightness(canvas) {
1158
  const context = canvas.getContext('2d', { willReadFrequently: true });
 
1159
  // Get the image data from the canvas
1160
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
1161
  const data = imageData.data;
 
1162
  let totalBrightness = 0;
1163
  let pixelCount = 0;
 
1164
  // Loop through each pixel
1165
  for (let i = 0; i < data.length; i += 4) {
1166
  const r = data[i]; // Red
1167
  const g = data[i + 1]; // Green
1168
  const b = data[i + 2]; // Blue
 
1169
  // Calculate brightness for this pixel
1170
  const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
1171
  totalBrightness += brightness;
1172
  pixelCount++;
1173
  }
 
1174
  // Calculate average brightness
1175
  const averageBrightness = totalBrightness / pixelCount;
 
1176
  return averageBrightness;
1177
  }
1178
  function triggerPulse() {
 
1180
  /* Apply after working on pulse css more
1181
  const resultWrapper = document.querySelector('.wrapper_result');
1182
  resultWrapper.classList.add('pulse');
 
1183
  // Remove the class after the animation ends to allow it to be triggered again
1184
  resultWrapper.addEventListener('animationend', () => {
1185
+ resultWrapper.classList.remove('pulse');
1186
  }, { once: true });*/
1187
  }
1188
+ </script>
1189
+
1190
+
1191
+
1192
+
1193
+
1194
 
1195
  </body>
1196
+
1197
  </html>
1198
+