alessandro trinca tornidor commited on
Commit
74a35d9
1 Parent(s): e8a1983

refactor: organize project with tests in a package, start following suggestions from pycharm, sonarlint and snyk

Browse files
.gitignore CHANGED
@@ -1,3 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Byte-compiled / optimized / DLL files
2
  __pycache__/
3
  *.py[cod]
@@ -20,12 +78,9 @@ parts/
20
  sdist/
21
  var/
22
  wheels/
23
- pip-wheel-metadata/
24
- share/python-wheels/
25
  *.egg-info/
26
  .installed.cfg
27
  *.egg
28
- MANIFEST
29
 
30
  # PyInstaller
31
  # Usually these files are written by a python script from a template
@@ -40,27 +95,19 @@ pip-delete-this-directory.txt
40
  # Unit test / coverage reports
41
  htmlcov/
42
  .tox/
43
- .nox/
44
  .coverage
45
  .coverage.*
46
  .cache
 
47
  nosetests.xml
48
  coverage.xml
49
  *.cover
50
- *.py,cover
51
  .hypothesis/
52
- .pytest_cache/
53
 
54
  # Translations
55
  *.mo
56
  *.pot
57
 
58
- # Django stuff:
59
- *.log
60
- local_settings.py
61
- db.sqlite3
62
- db.sqlite3-journal
63
-
64
  # Flask stuff:
65
  instance/
66
  .webassets-cache
@@ -69,7 +116,8 @@ instance/
69
  .scrapy
70
 
71
  # Sphinx documentation
72
- docs/_build/
 
73
 
74
  # PyBuilder
75
  target/
@@ -77,32 +125,18 @@ target/
77
  # Jupyter Notebook
78
  .ipynb_checkpoints
79
 
80
- # IPython
81
- profile_default/
82
- ipython_config.py
83
-
84
  # pyenv
85
  .python-version
86
 
87
- # pipenv
88
- # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
- # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
- # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
- # install all needed dependencies.
92
- #Pipfile.lock
93
-
94
- # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
- __pypackages__/
96
-
97
- # Celery stuff
98
- celerybeat-schedule
99
- celerybeat.pid
100
 
101
  # SageMath parsed files
102
  *.sage.py
103
 
104
  # Environments
105
  .env
 
106
  .venv
107
  env/
108
  venv/
@@ -122,8 +156,123 @@ venv.bak/
122
 
123
  # mypy
124
  .mypy_cache/
125
- .dmypy.json
126
- dmypy.json
127
 
128
- # Pyre type checker
129
- .pyre/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
3
+
4
+ ### Linux ###
5
+ *~
6
+
7
+ # temporary files which can be created if a process still has a handle open of a deleted file
8
+ .fuse_hidden*
9
+
10
+ # KDE directory preferences
11
+ .directory
12
+
13
+ # Linux trash folder which might appear on any partition or disk
14
+ .Trash-*
15
+
16
+ # .nfs files are created when an open file is removed but is still being accessed
17
+ .nfs*
18
+
19
+ ### OSX ###
20
+ *.DS_Store
21
+ .AppleDouble
22
+ .LSOverride
23
+
24
+ # Icon must end with two \r
25
+ Icon
26
+
27
+ # Thumbnails
28
+ ._*
29
+
30
+ # Files that might appear in the root of a volume
31
+ .DocumentRevisions-V100
32
+ .fseventsd
33
+ .Spotlight-V100
34
+ .TemporaryItems
35
+ .Trashes
36
+ .VolumeIcon.icns
37
+ .com.apple.timemachine.donotpresent
38
+
39
+ # Directories potentially created on remote AFP share
40
+ .AppleDB
41
+ .AppleDesktop
42
+ Network Trash Folder
43
+ Temporary Items
44
+ .apdisk
45
+
46
+ # CMake
47
+ cmake-build-debug/
48
+
49
+ # Ruby plugin and RubyMine
50
+ /.rakeTasks
51
+
52
+ # Crashlytics plugin (for Android Studio and IntelliJ)
53
+ com_crashlytics_export_strings.xml
54
+ crashlytics.properties
55
+ crashlytics-build.properties
56
+ fabric.properties
57
+
58
+ ### Python ###
59
  # Byte-compiled / optimized / DLL files
60
  __pycache__/
61
  *.py[cod]
 
78
  sdist/
79
  var/
80
  wheels/
 
 
81
  *.egg-info/
82
  .installed.cfg
83
  *.egg
 
84
 
85
  # PyInstaller
86
  # Usually these files are written by a python script from a template
 
95
  # Unit test / coverage reports
96
  htmlcov/
97
  .tox/
 
98
  .coverage
99
  .coverage.*
100
  .cache
101
+ .pytest_cache/
102
  nosetests.xml
103
  coverage.xml
104
  *.cover
 
105
  .hypothesis/
 
106
 
107
  # Translations
108
  *.mo
109
  *.pot
110
 
 
 
 
 
 
 
111
  # Flask stuff:
112
  instance/
113
  .webassets-cache
 
116
  .scrapy
117
 
118
  # Sphinx documentation
119
+ docs/_build/doctrees/*
120
+ docs/_build/html/*
121
 
122
  # PyBuilder
123
  target/
 
125
  # Jupyter Notebook
126
  .ipynb_checkpoints
127
 
 
 
 
 
128
  # pyenv
129
  .python-version
130
 
131
+ # celery beat schedule file
132
+ celerybeat-schedule.*
 
 
 
 
 
 
 
 
 
 
 
133
 
134
  # SageMath parsed files
135
  *.sage.py
136
 
137
  # Environments
138
  .env
139
+ .env*
140
  .venv
141
  env/
142
  venv/
 
156
 
157
  # mypy
158
  .mypy_cache/
 
 
159
 
160
+ ### VisualStudioCode ###
161
+ .vscode/*
162
+ !.vscode/settings.json
163
+ !.vscode/tasks.json
164
+ !.vscode/launch.json
165
+ !.vscode/extensions.json
166
+ .history
167
+
168
+ ### Windows ###
169
+ # Windows thumbnail cache files
170
+ Thumbs.db
171
+ ehthumbs.db
172
+ ehthumbs_vista.db
173
+
174
+ # Folder config file
175
+ Desktop.ini
176
+
177
+ # Recycle Bin used on file shares
178
+ $RECYCLE.BIN/
179
+
180
+ # Windows Installer files
181
+ *.cab
182
+ *.msi
183
+ *.msm
184
+ *.msp
185
+
186
+ # Windows shortcuts
187
+ *.lnk
188
+
189
+ # Build folder
190
+
191
+ */build/*
192
+
193
+ # custom
194
+ *.ori
195
+ tmp
196
+ nohup.out
197
+ /tests/events.tar
198
+ function_dump_*.json
199
+
200
+ # onnx models
201
+ *.onnx
202
+
203
+ # End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
204
+
205
+ ## .idea files
206
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
207
+ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
208
+
209
+ # User-specific stuff
210
+ .idea/**/workspace.xml
211
+ .idea/**/tasks.xml
212
+ .idea/**/usage.statistics.xml
213
+ .idea/**/dictionaries
214
+ .idea/**/shelf
215
+
216
+ # Generated files
217
+ .idea/**/contentModel.xml
218
+
219
+ # Sensitive or high-churn files
220
+ .idea/**/dataSources/
221
+ .idea/**/dataSources.ids
222
+ .idea/**/dataSources.local.xml
223
+ .idea/**/sqlDataSources.xml
224
+ .idea/**/dynamic.xml
225
+ .idea/**/uiDesigner.xml
226
+ .idea/**/dbnavigator.xml
227
+
228
+ # Gradle
229
+ .idea/**/gradle.xml
230
+ .idea/**/libraries
231
+
232
+ # Gradle and Maven with auto-import
233
+ # When using Gradle or Maven with auto-import, you should exclude module files,
234
+ # since they will be recreated, and may cause churn. Uncomment if using
235
+ # auto-import.
236
+ # .idea/modules.xml
237
+ .idea/*.iml
238
+ # .idea/modules
239
+
240
+ # CMake
241
+ cmake-build-*/
242
+
243
+ # Mongo Explorer plugin
244
+ .idea/**/mongoSettings.xml
245
+
246
+ # File-based project format
247
+ *.iws
248
+
249
+ # IntelliJ
250
+ out/
251
+
252
+ # mpeltonen/sbt-idea plugin
253
+ .idea_modules/
254
+
255
+ # JIRA plugin
256
+ atlassian-ide-plugin.xml
257
+
258
+ # Cursive Clojure plugin
259
+ .idea/replstate.xml
260
+
261
+ # Crashlytics plugin (for Android Studio and IntelliJ)
262
+ com_crashlytics_export_strings.xml
263
+ crashlytics.properties
264
+ crashlytics-build.properties
265
+ fabric.properties
266
+
267
+ # Editor-based Rest Client
268
+ .idea/httpRequests
269
+
270
+ # Android studio 3.1+ serialized cache file
271
+ .idea/caches/build_file_checksums.ser
272
+
273
+ # Sonarlint plugin
274
+ .idea/sonarlint
275
+ /.idea/modules.xml
276
+
277
+ # node_modules
278
+ node_modules
WordMatching.py → aip_trainer/WordMatching.py RENAMED
@@ -1,9 +1,11 @@
1
- import WordMetrics
2
- from ortools.sat.python import cp_model
3
- import numpy as np
4
  from string import punctuation
 
 
5
  from dtwalign import dtw_from_distance_matrix
6
- import time
 
 
7
 
8
  offset_blank = 1
9
  TIME_THRESHOLD_MAPPING = 5.0
@@ -77,7 +79,8 @@ def get_best_path_from_distance_matrix(word_distance_matrix):
77
  (solver.Value(estimated_words_order[word_idx])))
78
 
79
  return np.array(mapped_indices, dtype=int)
80
- except:
 
81
  return []
82
 
83
 
 
1
+ import time
 
 
2
  from string import punctuation
3
+
4
+ import numpy as np
5
  from dtwalign import dtw_from_distance_matrix
6
+ from ortools.sat.python import cp_model
7
+
8
+ from . import WordMetrics, app_logger
9
 
10
  offset_blank = 1
11
  TIME_THRESHOLD_MAPPING = 5.0
 
79
  (solver.Value(estimated_words_order[word_idx])))
80
 
81
  return np.array(mapped_indices, dtype=int)
82
+ except Exception as ex:
83
+ app_logger.error(f"ex:{ex}.")
84
  return []
85
 
86
 
WordMetrics.py → aip_trainer/WordMetrics.py RENAMED
@@ -1,8 +1,12 @@
1
  import numpy as np
2
 
 
 
 
3
  # ref from https://gitlab.com/-/snippets/1948157
4
  # For some variants, look here https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Python
5
 
 
6
  # Pure python
7
  def edit_distance_python2(a, b):
8
  # This version is commutative, so as an optimization we force |a|>=|b|
@@ -52,5 +56,5 @@ def edit_distance_python(seq1, seq2):
52
  matrix[x-1,y-1] + 1,
53
  matrix[x,y-1] + 1
54
  )
55
- #print (matrix)
56
  return matrix[size_x - 1, size_y - 1]
 
1
  import numpy as np
2
 
3
+ from aip_trainer import app_logger
4
+
5
+
6
  # ref from https://gitlab.com/-/snippets/1948157
7
  # For some variants, look here https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Python
8
 
9
+
10
  # Pure python
11
  def edit_distance_python2(a, b):
12
  # This version is commutative, so as an optimization we force |a|>=|b|
 
56
  matrix[x-1,y-1] + 1,
57
  matrix[x,y-1] + 1
58
  )
59
+ app_logger.debug("matrix:{}\n".format(matrix))
60
  return matrix[size_x - 1, size_y - 1]
aip_trainer/__init__.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Get machine learning predictions from geodata raster images"""
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import structlog
6
+ from dotenv import load_dotenv
7
+
8
+ from aip_trainer.utils import session_logger
9
+
10
+
11
+ load_dotenv()
12
+ PROJECT_ROOT_FOLDER = Path(globals().get("__file__", "./_")).absolute().parent.parent
13
+ LOG_JSON_FORMAT = bool(os.getenv("LOG_JSON_FORMAT", False))
14
+ log_level = os.getenv("LOG_LEVEL", "INFO")
15
+ session_logger.setup_logging(json_logs=LOG_JSON_FORMAT, log_level=log_level)
16
+ app_logger = structlog.stdlib.get_logger(__name__)
aip_trainer/lambdas/__init__.py ADDED
File without changes
data_de_en_2.pickle → aip_trainer/lambdas/data_de_en_2.pickle RENAMED
File without changes
lambdaGetSample.py → aip_trainer/lambdas/lambdaGetSample.py RENAMED
@@ -1,10 +1,12 @@
1
-
2
- import pandas as pd
3
  import json
4
- import RuleBasedModels
5
- import epitran
6
- import random
7
  import pickle
 
 
 
 
 
 
 
8
 
9
 
10
  class TextDataset:
@@ -27,11 +29,11 @@ class TextDataset:
27
  return self.number_of_samples
28
 
29
 
30
- sample_folder = "./"
31
  lambda_database = {}
32
  lambda_ipa_converter = {}
33
 
34
- with open(sample_folder+'data_de_en_2.pickle', 'rb') as handle:
35
  df = pickle.load(handle)
36
 
37
  lambda_database['de'] = TextDataset(df, 'de')
 
 
 
1
  import json
 
 
 
2
  import pickle
3
+ import random
4
+ from pathlib import Path
5
+
6
+ import epitran
7
+
8
+ from aip_trainer import PROJECT_ROOT_FOLDER
9
+ from aip_trainer.models import RuleBasedModels
10
 
11
 
12
  class TextDataset:
 
29
  return self.number_of_samples
30
 
31
 
32
+ sample_folder = Path(PROJECT_ROOT_FOLDER / "aip_trainer" / "lambdas")
33
  lambda_database = {}
34
  lambda_ipa_converter = {}
35
 
36
+ with open(sample_folder / 'data_de_en_2.pickle', 'rb') as handle:
37
  df = pickle.load(handle)
38
 
39
  lambda_database['de'] = TextDataset(df, 'de')
lambdaSpeechToScore.py → aip_trainer/lambdas/lambdaSpeechToScore.py RENAMED
@@ -1,16 +1,18 @@
1
 
2
- import torch
3
  import json
4
  import os
5
- import WordMatching as wm
6
- import utilsFileIO
7
- import pronunciationTrainer
8
- import base64
9
  import time
 
10
  import audioread
11
  import numpy as np
 
12
  from torchaudio.transforms import Resample
13
 
 
 
 
 
14
 
15
  trainer_SST_lambda = {
16
  'de': pronunciationTrainer.getTrainer("de"),
@@ -42,25 +44,28 @@ def lambda_handler(event, context):
42
  }
43
 
44
  start = time.time()
45
- random_file_name = './'+utilsFileIO.generateRandomString()+'.ogg'
46
  f = open(random_file_name, 'wb')
47
  f.write(file_bytes)
48
  f.close()
49
- print('Time for saving binary in file: ', str(time.time()-start))
 
50
 
51
  start = time.time()
52
  signal, fs = audioread_load(random_file_name)
53
 
54
  signal = transform(torch.Tensor(signal)).unsqueeze(0)
55
 
56
- print('Time for loading .ogg file file: ', str(time.time()-start))
 
57
 
58
  result = trainer_SST_lambda[language].processAudioForGivenText(
59
  signal, real_text)
60
 
61
  start = time.time()
62
  os.remove(random_file_name)
63
- print('Time for deleting file: ', str(time.time()-start))
 
64
 
65
  start = time.time()
66
  real_transcripts_ipa = ' '.join(
@@ -90,7 +95,8 @@ def lambda_handler(event, context):
90
 
91
  pair_accuracy_category = ' '.join(
92
  [str(category) for category in result['pronunciation_categories']])
93
- print('Time to post-process results: ', str(time.time()-start))
 
94
 
95
  res = {'real_transcript': result['recording_transcript'],
96
  'ipa_transcript': result['recording_ipa'],
 
1
 
2
+ import base64
3
  import json
4
  import os
 
 
 
 
5
  import time
6
+
7
  import audioread
8
  import numpy as np
9
+ import torch
10
  from torchaudio.transforms import Resample
11
 
12
+ from aip_trainer import WordMatching as wm, app_logger
13
+ from aip_trainer import pronunciationTrainer
14
+ from aip_trainer import utilsFileIO
15
+
16
 
17
  trainer_SST_lambda = {
18
  'de': pronunciationTrainer.getTrainer("de"),
 
44
  }
45
 
46
  start = time.time()
47
+ random_file_name = './' + utilsFileIO.generateRandomString() + '.ogg'
48
  f = open(random_file_name, 'wb')
49
  f.write(file_bytes)
50
  f.close()
51
+ duration = time.time() - start
52
+ app_logger.info(f'Time for saving binary in file: {duration}.')
53
 
54
  start = time.time()
55
  signal, fs = audioread_load(random_file_name)
56
 
57
  signal = transform(torch.Tensor(signal)).unsqueeze(0)
58
 
59
+ duration = time.time() - start
60
+ app_logger.info(f'Time for loading .ogg file file: {duration}.')
61
 
62
  result = trainer_SST_lambda[language].processAudioForGivenText(
63
  signal, real_text)
64
 
65
  start = time.time()
66
  os.remove(random_file_name)
67
+ duration = time.time() - start
68
+ app_logger.info(f'Time for deleting file: {duration}')
69
 
70
  start = time.time()
71
  real_transcripts_ipa = ' '.join(
 
95
 
96
  pair_accuracy_category = ' '.join(
97
  [str(category) for category in result['pronunciation_categories']])
98
+ duration = time.time() - start
99
+ app_logger.info(f'Time to post-process results: {duration}')
100
 
101
  res = {'real_transcript': result['recording_transcript'],
102
  'ipa_transcript': result['recording_ipa'],
aip_trainer/lambdas/lambdaTTS.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import base64
3
+ import json
4
+ import tempfile
5
+
6
+ import soundfile as sf
7
+
8
+ from aip_trainer import app_logger
9
+ from aip_trainer.models.models import getTTSModel
10
+ from aip_trainer.models.AIModels import NeuralTTS
11
+
12
+
13
+ sampling_rate = 16000
14
+ model_de = getTTSModel('de')
15
+ model_TTS_lambda = NeuralTTS(model_de, sampling_rate)
16
+
17
+
18
+ def lambda_handler(event, context):
19
+
20
+ body = json.loads(event['body'])
21
+
22
+ text_string = body['value']
23
+
24
+ linear_factor = 0.2
25
+ audio = model_TTS_lambda.getAudioFromSentence(
26
+ text_string).detach().numpy()*linear_factor
27
+ with tempfile.TemporaryFile(prefix="temp_sound_", suffix=".wav") as f1:
28
+ app_logger.info(f"Saving temp audio to {f1.name}...")
29
+ # random_file_name = utilsFileIO.generateRandomString(20) + '.wav'
30
+ # sf.write('./'+random_file_name, audio, 16000)
31
+
32
+ sf.write(f1.name, audio, sampling_rate)
33
+ with open(f1.name, "rb") as f:
34
+ audio_byte_array = f.read()
35
+ # os.remove(random_file_name)
36
+ return {
37
+ 'statusCode': 200,
38
+ 'headers': {
39
+ 'Access-Control-Allow-Headers': '*',
40
+ 'Access-Control-Allow-Origin': '*',
41
+ 'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
42
+ },
43
+ 'body': json.dumps(
44
+ {
45
+ "wavBase64": str(base64.b64encode(audio_byte_array))[2:-1],
46
+ },
47
+ )
48
+ }
AIModels.py → aip_trainer/models/AIModels.py RENAMED
@@ -1,6 +1,7 @@
1
- import ModelInterfaces
2
- import torch
3
  import numpy as np
 
 
 
4
 
5
 
6
  class NeuralASR(ModelInterfaces.IASRModel):
 
 
 
1
  import numpy as np
2
+ import torch
3
+
4
+ from aip_trainer.models import ModelInterfaces
5
 
6
 
7
  class NeuralASR(ModelInterfaces.IASRModel):
ModelInterfaces.py → aip_trainer/models/ModelInterfaces.py RENAMED
@@ -1,5 +1,5 @@
1
-
2
  import abc
 
3
  import numpy as np
4
 
5
 
 
 
1
  import abc
2
+
3
  import numpy as np
4
 
5
 
RuleBasedModels.py → aip_trainer/models/RuleBasedModels.py RENAMED
@@ -1,9 +1,7 @@
1
- import ModelInterfaces
2
- import torch
3
- import numpy as np
4
- import epitran
5
  import eng_to_ipa
6
 
 
 
7
 
8
  class EpitranPhonemConverter(ModelInterfaces.ITextToPhonemModel):
9
  word_locations_in_samples = None
 
 
 
 
 
1
  import eng_to_ipa
2
 
3
+ from aip_trainer.models import ModelInterfaces
4
+
5
 
6
  class EpitranPhonemConverter(ModelInterfaces.ITextToPhonemModel):
7
  word_locations_in_samples = None
aip_trainer/models/__init__.py ADDED
File without changes
models.py → aip_trainer/models/models.py RENAMED
@@ -4,9 +4,6 @@ import torch.nn as nn
4
  import pickle
5
 
6
 
7
- import pickle
8
-
9
-
10
  def getASRModel(language: str) -> nn.Module:
11
 
12
  if language == 'de':
 
4
  import pickle
5
 
6
 
 
 
 
7
  def getASRModel(language: str) -> nn.Module:
8
 
9
  if language == 'de':
pronunciationTrainer.py → aip_trainer/pronunciationTrainer.py RENAMED
@@ -1,15 +1,14 @@
 
 
1
 
2
- import torch
3
- import numpy as np
4
- import models as mo
5
- import WordMetrics
6
- import WordMatching as wm
7
  import epitran
8
- import ModelInterfaces as mi
9
- import AIModels
10
- import RuleBasedModels
11
- from string import punctuation
12
- import time
 
 
13
 
14
 
15
  def getTrainer(language: str):
@@ -66,7 +65,7 @@ class PronunciationTrainer:
66
  def getWordsRelativeIntonation(self, Audio: torch.tensor, word_locations: list):
67
  intonations = torch.zeros((len(word_locations), 1))
68
  intonation_fade_samples = 0.3*self.sampling_rate
69
- print(intonations.shape)
70
  for word in range(len(word_locations)):
71
  intonation_start = int(np.maximum(
72
  0, word_locations[word][0]-intonation_fade_samples))
@@ -85,12 +84,15 @@ class PronunciationTrainer:
85
  start = time.time()
86
  recording_transcript, recording_ipa, word_locations = self.getAudioTranscript(
87
  recordedAudio)
88
- print('Time for NN to transcript audio: ', str(time.time()-start))
 
 
89
 
90
  start = time.time()
91
  real_and_transcribed_words, real_and_transcribed_words_ipa, mapped_words_indices = self.matchSampleAndRecordedWords(
92
  real_text, recording_transcript)
93
- print('Time for matching transcripts: ', str(time.time()-start))
 
94
 
95
  start_time, end_time = self.getWordLocationsFromRecordInSeconds(
96
  word_locations, mapped_words_indices)
 
1
+ import time
2
+ from string import punctuation
3
 
 
 
 
 
 
4
  import epitran
5
+ import numpy as np
6
+ import torch
7
+
8
+ from . import WordMatching as wm
9
+ from . import WordMetrics
10
+ from . import app_logger
11
+ from .models import AIModels, ModelInterfaces as mi, RuleBasedModels, models as mo
12
 
13
 
14
  def getTrainer(language: str):
 
65
  def getWordsRelativeIntonation(self, Audio: torch.tensor, word_locations: list):
66
  intonations = torch.zeros((len(word_locations), 1))
67
  intonation_fade_samples = 0.3*self.sampling_rate
68
+ app_logger.info(intonations.shape)
69
  for word in range(len(word_locations)):
70
  intonation_start = int(np.maximum(
71
  0, word_locations[word][0]-intonation_fade_samples))
 
84
  start = time.time()
85
  recording_transcript, recording_ipa, word_locations = self.getAudioTranscript(
86
  recordedAudio)
87
+
88
+ duration = time.time() - start
89
+ app_logger.info(f'Time for NN to transcript audio: {duration}.')
90
 
91
  start = time.time()
92
  real_and_transcribed_words, real_and_transcribed_words_ipa, mapped_words_indices = self.matchSampleAndRecordedWords(
93
  real_text, recording_transcript)
94
+ duration = time.time() - start
95
+ app_logger.info(f'Time for matching transcripts: {duration}.')
96
 
97
  start_time, end_time = self.getWordLocationsFromRecordInSeconds(
98
  word_locations, mapped_words_indices)
aip_trainer/utils/__init__.py ADDED
File without changes
aip_trainer/utils/session_logger.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import sys
3
+
4
+ import structlog
5
+ from structlog.types import EventDict, Processor
6
+
7
+
8
+ # https://github.com/hynek/structlog/issues/35#issuecomment-591321744
9
+ def rename_event_key(_, __, event_dict: EventDict) -> EventDict:
10
+ """
11
+ Log entries keep the text message in the `event` field, but Datadog
12
+ uses the `message` field. This processor moves the value from one field to
13
+ the other.
14
+ See https://github.com/hynek/structlog/issues/35#issuecomment-591321744
15
+ """
16
+ event_dict["message"] = event_dict.pop("event")
17
+ return event_dict
18
+
19
+
20
+ def drop_color_message_key(_, __, event_dict: EventDict) -> EventDict:
21
+ """
22
+ Uvicorn logs the message a second time in the extra `color_message`, but we don't
23
+ need it. This processor drops the key from the event dict if it exists.
24
+ """
25
+ event_dict.pop("color_message", None)
26
+ return event_dict
27
+
28
+
29
+ def setup_logging(json_logs: bool = False, log_level: str = "INFO"):
30
+ """Enhance the configuration of structlog.
31
+ Needed for correlation id injection with fastapi middleware in samgis-web.
32
+ After the use of logging_middleware() in samgis_web.web.middlewares, add also the CorrelationIdMiddleware from
33
+ 'asgi_correlation_id' package. (See 'tests/web/test_middlewares.py' in samgis_web).
34
+ To change an input parameter like the log level, re-run the function changing the parameter
35
+ (no need to re-instantiate the logger instance: it's a hot change)
36
+
37
+ Args:
38
+ json_logs: set logs in json format
39
+ log_level: log level string
40
+
41
+ Returns:
42
+
43
+ """
44
+ timestamper = structlog.processors.TimeStamper(fmt="iso")
45
+
46
+ shared_processors: list[Processor] = [
47
+ structlog.contextvars.merge_contextvars,
48
+ structlog.stdlib.add_logger_name,
49
+ structlog.stdlib.add_log_level,
50
+ structlog.stdlib.PositionalArgumentsFormatter(),
51
+ structlog.stdlib.ExtraAdder(),
52
+ drop_color_message_key,
53
+ timestamper,
54
+ structlog.processors.StackInfoRenderer(),
55
+ # adapted from https://www.structlog.org/en/stable/standard-library.html
56
+ # If the "exc_info" key in the event dict is either true or a
57
+ # sys.exc_info() tuple, remove "exc_info" and render the exception
58
+ # with traceback into the "exception" key.
59
+ structlog.processors.format_exc_info,
60
+ # If some value is in bytes, decode it to a Unicode str.
61
+ structlog.processors.UnicodeDecoder(),
62
+ # Add callsite parameters.
63
+ structlog.processors.CallsiteParameterAdder(
64
+ {
65
+ structlog.processors.CallsiteParameter.FUNC_NAME,
66
+ structlog.processors.CallsiteParameter.LINENO,
67
+ }
68
+ ),
69
+ # Render the final event dict as JSON.
70
+ ]
71
+
72
+ if json_logs:
73
+ # We rename the `event` key to `message` only in JSON logs, as Datadog looks for the
74
+ # `message` key but the pretty ConsoleRenderer looks for `event`
75
+ shared_processors.append(rename_event_key)
76
+ # Format the exception only for JSON logs, as we want to pretty-print them when
77
+ # using the ConsoleRenderer
78
+ shared_processors.append(structlog.processors.format_exc_info)
79
+
80
+ structlog.configure(
81
+ processors=shared_processors
82
+ + [
83
+ # Prepare event dict for `ProcessorFormatter`.
84
+ structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
85
+ ],
86
+ logger_factory=structlog.stdlib.LoggerFactory(),
87
+ cache_logger_on_first_use=True,
88
+ )
89
+
90
+ log_renderer: structlog.types.Processor
91
+ if json_logs:
92
+ log_renderer = structlog.processors.JSONRenderer()
93
+ else:
94
+ log_renderer = structlog.dev.ConsoleRenderer()
95
+
96
+ formatter = structlog.stdlib.ProcessorFormatter(
97
+ # These run ONLY on `logging` entries that do NOT originate within
98
+ # structlog.
99
+ foreign_pre_chain=shared_processors,
100
+ # These run on ALL entries after the pre_chain is done.
101
+ processors=[
102
+ # Remove _record & _from_structlog.
103
+ structlog.stdlib.ProcessorFormatter.remove_processors_meta,
104
+ log_renderer,
105
+ ],
106
+ )
107
+
108
+ handler = logging.StreamHandler()
109
+ # Use OUR `ProcessorFormatter` to format all `logging` entries.
110
+ handler.setFormatter(formatter)
111
+ root_logger = logging.getLogger()
112
+ root_logger.addHandler(handler)
113
+ root_logger.setLevel(log_level.upper())
114
+
115
+ for _log in ["uvicorn", "uvicorn.error"]:
116
+ # Clear the log handlers for uvicorn loggers, and enable propagation
117
+ # so the messages are caught by our root logger and formatted correctly
118
+ # by structlog
119
+ logging.getLogger(_log).handlers.clear()
120
+ logging.getLogger(_log).propagate = True
121
+
122
+ # Since we re-create the access logs ourselves, to add all information
123
+ # in the structured log (see the `logging_middleware` in main.py), we clear
124
+ # the handlers and prevent the logs to propagate to a logger higher up in the
125
+ # hierarchy (effectively rendering them silent).
126
+ logging.getLogger("uvicorn.access").handlers.clear()
127
+ logging.getLogger("uvicorn.access").propagate = False
128
+
129
+ def handle_exception(exc_type, exc_value, exc_traceback):
130
+ """
131
+ Log any uncaught exception instead of letting it be printed by Python
132
+ (but leave KeyboardInterrupt untouched to allow users to Ctrl+C to stop)
133
+ See https://stackoverflow.com/a/16993115/3641865
134
+ """
135
+ if issubclass(exc_type, KeyboardInterrupt):
136
+ sys.__excepthook__(exc_type, exc_value, exc_traceback)
137
+ return
138
+
139
+ root_logger.error(
140
+ "Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)
141
+ )
142
+
143
+ sys.excepthook = handle_exception
utilsFileIO.py → aip_trainer/utilsFileIO.py RENAMED
File without changes
lambdaTTS.py DELETED
@@ -1,46 +0,0 @@
1
-
2
- import models
3
- import soundfile as sf
4
- import json
5
- import AIModels
6
- #from flask import Response
7
- import utilsFileIO
8
- import os
9
- import base64
10
-
11
- sampling_rate = 16000
12
- model_TTS_lambda = AIModels.NeuralTTS(models.getTTSModel('de'), sampling_rate)
13
-
14
-
15
- def lambda_handler(event, context):
16
-
17
- body = json.loads(event['body'])
18
-
19
- text_string = body['value']
20
-
21
- linear_factor = 0.2
22
- audio = model_TTS_lambda.getAudioFromSentence(
23
- text_string).detach().numpy()*linear_factor
24
- random_file_name = utilsFileIO.generateRandomString(20)+'.wav'
25
-
26
- sf.write('./'+random_file_name, audio, 16000)
27
-
28
- with open(random_file_name, "rb") as f:
29
- audio_byte_array = f.read()
30
-
31
- os.remove(random_file_name)
32
-
33
-
34
- return {
35
- 'statusCode': 200,
36
- 'headers': {
37
- 'Access-Control-Allow-Headers': '*',
38
- 'Access-Control-Allow-Origin': '*',
39
- 'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
40
- },
41
- 'body': json.dumps(
42
- {
43
- "wavBase64": str(base64.b64encode(audio_byte_array))[2:-1],
44
- },
45
- )
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/javascript/callbacks.js CHANGED
@@ -105,7 +105,7 @@ const UIRecordingError = () => {
105
  //################### Application state functions #######################
106
  function updateScore(currentPronunciationScore) {
107
 
108
- if (isNaN(currentPronunciationScore))
109
  return;
110
  currentScore += currentPronunciationScore * scoreMultiplier;
111
  currentScore = Math.round(currentScore);
 
105
  //################### Application state functions #######################
106
  function updateScore(currentPronunciationScore) {
107
 
108
+ if (Number.isNaN(currentPronunciationScore))
109
  return;
110
  currentScore += currentPronunciationScore * scoreMultiplier;
111
  currentScore = Math.round(currentScore);
tests/__init__.py ADDED
File without changes
unitTests.py → tests/unitTests.py RENAMED
@@ -1,11 +1,11 @@
 
1
  import unittest
2
 
3
- import ModelInterfaces
4
- import lambdaGetSample
5
- import RuleBasedModels
6
  import epitran
7
- import json
8
- import pronunciationTrainer
 
 
9
 
10
 
11
  def test_category(category: int, threshold_min: int, threshold_max: int):
@@ -40,12 +40,12 @@ class TestDataset(unittest.TestCase):
40
  self.assertTrue(test_category(3, 20, 10000))
41
 
42
 
43
- def check_phonem_converter(converter: ModelInterfaces.ITextToPhonemModel, input: str, expected_output: str):
44
- output = converter.convertToPhonem(input)
45
 
46
  is_correct = output == expected_output
47
  if not is_correct:
48
- print('Conversion from "', input, '" should be "',
49
  expected_output, '", but was "', output, '"')
50
  return is_correct
51
 
@@ -60,7 +60,6 @@ class TestPhonemConverter(unittest.TestCase):
60
  def test_german(self):
61
  phonem_converter = RuleBasedModels.EpitranPhonemConverter(
62
  epitran.Epitran('deu-Latn'))
63
-
64
  self.assertTrue(check_phonem_converter(
65
  phonem_converter, 'Hallo, das ist ein Test', 'haloː, dɑːs ɪst ain tɛst'))
66
 
 
1
+ import json
2
  import unittest
3
 
 
 
 
4
  import epitran
5
+
6
+ from aip_trainer.models import ModelInterfaces, RuleBasedModels
7
+ from aip_trainer import pronunciationTrainer
8
+ from aip_trainer.lambdas import lambdaGetSample
9
 
10
 
11
  def test_category(category: int, threshold_min: int, threshold_max: int):
 
40
  self.assertTrue(test_category(3, 20, 10000))
41
 
42
 
43
+ def check_phonem_converter(converter: ModelInterfaces.ITextToPhonemModel, input_phonem: str, expected_output: str):
44
+ output = converter.convertToPhonem(input_phonem)
45
 
46
  is_correct = output == expected_output
47
  if not is_correct:
48
+ print('Conversion from "', input_phonem, '" should be "',
49
  expected_output, '", but was "', output, '"')
50
  return is_correct
51
 
 
60
  def test_german(self):
61
  phonem_converter = RuleBasedModels.EpitranPhonemConverter(
62
  epitran.Epitran('deu-Latn'))
 
63
  self.assertTrue(check_phonem_converter(
64
  phonem_converter, 'Hallo, das ist ein Test', 'haloː, dɑːs ɪst ain tɛst'))
65
 
webApp.py CHANGED
@@ -4,9 +4,10 @@ import os
4
  from flask_cors import CORS
5
  import json
6
 
7
- import lambdaTTS
8
- import lambdaSpeechToScore
9
- import lambdaGetSample
 
10
 
11
  app = Flask(__name__)
12
  cors = CORS(app)
 
4
  from flask_cors import CORS
5
  import json
6
 
7
+ from aip_trainer.lambdas import lambdaTTS
8
+ from aip_trainer.lambdas import lambdaSpeechToScore
9
+ from aip_trainer.lambdas import lambdaGetSample
10
+
11
 
12
  app = Flask(__name__)
13
  cors = CORS(app)