Anchor-annotator 0.5.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
anchor/plot.py CHANGED
@@ -11,6 +11,7 @@ import numpy as np
11
11
  import pyqtgraph as pg
12
12
  import sqlalchemy
13
13
  from Bio import pairwise2
14
+ from line_profiler_pycharm import profile
14
15
  from montreal_forced_aligner.data import CtmInterval
15
16
  from montreal_forced_aligner.db import Speaker, Utterance
16
17
  from montreal_forced_aligner.dictionary.mixins import (
@@ -375,11 +376,12 @@ class AudioPlotItem(pg.PlotItem):
375
376
  def __init__(self, top_point, bottom_point):
376
377
  super().__init__(axisItems={"bottom": TimeAxis("bottom")})
377
378
  self.settings = AnchorSettings()
379
+ self.plot_theme = self.settings.plot_theme
378
380
  self.setDefaultPadding(0)
379
381
  self.setClipToView(True)
380
382
 
381
- self.getAxis("bottom").setPen(self.settings.plot_theme.break_line_color)
382
- self.getAxis("bottom").setTextPen(self.settings.plot_theme.break_line_color)
383
+ self.getAxis("bottom").setPen(self.plot_theme.break_line_color)
384
+ self.getAxis("bottom").setTextPen(self.plot_theme.break_line_color)
383
385
  self.getAxis("bottom").setTickFont(self.settings.small_font)
384
386
  rect = QtCore.QRectF()
385
387
  rect.setTop(top_point)
@@ -467,8 +469,8 @@ class UtteranceView(QtWidgets.QWidget):
467
469
  )
468
470
  self.audio_layout.centralWidget.layout.setContentsMargins(0, 0, 0, 0)
469
471
  self.audio_layout.centralWidget.layout.setSpacing(0)
470
- plot_theme = self.settings.plot_theme
471
- self.audio_layout.setBackground(plot_theme.background_color)
472
+ self.plot_theme = self.settings.plot_theme
473
+ self.audio_layout.setBackground(self.plot_theme.background_color)
472
474
  self.audio_plot = AudioPlots(2, 1, 0)
473
475
  self.audio_plot_item = AudioPlotItem(2, 0)
474
476
  self.audio_plot_item.addItem(self.audio_plot)
@@ -485,7 +487,7 @@ class UtteranceView(QtWidgets.QWidget):
485
487
  self.speaker_tier_layout.setAspectLocked(False)
486
488
  self.speaker_tier_layout.centralWidget.layout.setContentsMargins(0, 0, 0, 0)
487
489
  self.speaker_tier_layout.centralWidget.layout.setSpacing(0)
488
- self.speaker_tier_layout.setBackground(plot_theme.background_color)
490
+ self.speaker_tier_layout.setBackground(self.plot_theme.background_color)
489
491
  self.speaker_tiers: dict[SpeakerTier] = {}
490
492
  self.speaker_tier_items = {}
491
493
  self.search_term = None
@@ -539,15 +541,13 @@ class UtteranceView(QtWidgets.QWidget):
539
541
  self.corpus_model.refreshTiers.connect(self.finalize_loading_utterances)
540
542
 
541
543
  def refresh_theme(self):
542
- self.audio_layout.setBackground(self.settings.plot_theme.background_color)
543
- self.speaker_tier_layout.setBackground(self.settings.plot_theme.background_color)
544
+ self.audio_layout.setBackground(self.plot_theme.background_color)
545
+ self.speaker_tier_layout.setBackground(self.plot_theme.background_color)
544
546
  self.audio_plot.wave_form.update_theme()
545
547
  self.audio_plot.spectrogram.update_theme()
546
548
  self.audio_plot.pitch_track.update_theme()
547
- self.audio_plot_item.getAxis("bottom").setPen(self.settings.plot_theme.break_line_color)
548
- self.audio_plot_item.getAxis("bottom").setTextPen(
549
- self.settings.plot_theme.break_line_color
550
- )
549
+ self.audio_plot_item.getAxis("bottom").setPen(self.plot_theme.break_line_color)
550
+ self.audio_plot_item.getAxis("bottom").setTextPen(self.plot_theme.break_line_color)
551
551
 
552
552
  def refresh(self):
553
553
  self.finalize_loading_utterances()
@@ -890,17 +890,15 @@ class Menu(QtWidgets.QMenu):
890
890
 
891
891
 
892
892
  class TextEdit(QtWidgets.QTextEdit):
893
- lookUpWord = QtCore.Signal(object)
894
- createWord = QtCore.Signal(object)
895
893
  lostFocus = QtCore.Signal()
894
+ gainedFocus = QtCore.Signal()
895
+ menuRequested = QtCore.Signal(object, object)
896
896
 
897
897
  def __init__(self, dictionary_model, speaker_id, *args):
898
898
  super().__init__(*args)
899
899
  self.settings = AnchorSettings()
900
900
  self.dictionary_model: DictionaryTableModel = dictionary_model
901
901
  self.speaker_id = speaker_id
902
- self.lookUpWord.connect(self.dictionary_model.lookup_word)
903
- self.createWord.connect(self.dictionary_model.add_word)
904
902
  self.setCursor(QtCore.Qt.CursorShape.IBeamCursor)
905
903
  self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
906
904
 
@@ -925,35 +923,25 @@ class TextEdit(QtWidgets.QTextEdit):
925
923
  self.lostFocus.emit()
926
924
  return super().focusOutEvent(e)
927
925
 
926
+ def focusInEvent(self, e: QtGui.QFocusEvent) -> None:
927
+ self.gainedFocus.emit()
928
+ return super().focusInEvent(e)
929
+
928
930
  def generate_context_menu(self, location):
929
- menu = Menu(self)
930
931
  cursor = self.cursorForPosition(location)
931
932
  cursor.select(QtGui.QTextCursor.SelectionType.WordUnderCursor)
932
933
  word = cursor.selectedText()
933
- # add extra items to the menu
934
- menu.addSeparator()
935
- if self.dictionary_model.check_word(word, speaker_id=self.speaker_id):
936
- lookUpAction = QtGui.QAction(f'Look up "{word}" in dictionary', self)
937
- lookUpAction.triggered.connect(lambda: self.lookUpWord.emit(word))
938
- lookUpAction.triggered.connect(menu.hide)
939
- menu.addAction(lookUpAction)
940
- else:
941
- createAction = QtGui.QAction(f'Add pronunciation for "{word}"', self)
942
- createAction.triggered.connect(lambda: self.createWord.emit(word))
943
- createAction.triggered.connect(menu.hide)
944
- menu.addAction(createAction)
945
- menu.setStyleSheet(self.settings.menu_style_sheet)
946
- # show the menu
947
- menu.exec_(self.mapToGlobal(location))
934
+ self.menuRequested.emit(word, self.mapToGlobal(location))
948
935
 
949
936
 
950
937
  class UtterancePGTextItem(pg.TextItem):
938
+ @profile
951
939
  def __init__(
952
940
  self,
953
941
  begin: float,
954
942
  end: float,
955
943
  text: str,
956
- selection_model: CorpusSelectionModel,
944
+ selection_model: FileSelectionModel,
957
945
  top_point=None,
958
946
  bottom_point=None,
959
947
  per_tier_range=None,
@@ -962,6 +950,7 @@ class UtterancePGTextItem(pg.TextItem):
962
950
  fill=None,
963
951
  dictionary_model: Optional[DictionaryTableModel] = None,
964
952
  speaker_id: int = 0,
953
+ editable: bool = True,
965
954
  ):
966
955
  self.anchor = pg.Point(anchor)
967
956
  self.rotateAxis = None
@@ -974,17 +963,16 @@ class UtterancePGTextItem(pg.TextItem):
974
963
  self.dictionary_model = dictionary_model
975
964
  self.speaker_id = speaker_id
976
965
  pg.GraphicsObject.__init__(self)
966
+ self.editable = editable
977
967
  self.text_edit = TextEdit(dictionary_model, speaker_id)
978
968
  self.text_edit.cursorPositionChanged.connect(self.update)
979
969
 
980
- # self.text_edit.setAutoFillBackground(False)
981
- # self.text_edit.viewport().setAutoFillBackground(False)
982
970
  self.textItem = QtWidgets.QGraphicsProxyWidget(self)
983
971
  self.textItem.setWidget(self.text_edit)
972
+ self.text_edit.setPlainText(text)
984
973
  self._lastTransform = None
985
974
  self._lastScene = None
986
975
  self._bounds = QtCore.QRectF()
987
- self.text_edit.setPlainText(text)
988
976
  self.fill = pg.mkBrush(fill)
989
977
  self.border = pg.mkPen(border)
990
978
  self._cached_pixel_size = None
@@ -994,9 +982,23 @@ class UtterancePGTextItem(pg.TextItem):
994
982
  self.per_tier_range = per_tier_range
995
983
  self.view_min = self.selection_model.plot_min
996
984
  self.view_max = self.selection_model.plot_max
997
- self.selection_model.viewChanged.connect(self.update_times)
985
+ self.selection_model.viewChanged.connect(self.update_view_times)
998
986
 
999
987
  def update_times(self, begin, end):
988
+ self.begin = begin
989
+ self.end = end
990
+ if self.end <= self.view_min or self.begin >= self.view_max:
991
+ return
992
+ self.hide()
993
+ if (
994
+ self.view_min <= self.begin < self.view_max
995
+ or self.view_max >= self.end > self.view_min
996
+ or (self.begin <= self.view_min and self.end >= self.view_max)
997
+ ):
998
+ self.update_pos()
999
+ self.show()
1000
+
1001
+ def update_view_times(self, begin, end):
1000
1002
  self.view_min = begin
1001
1003
  self.view_max = end
1002
1004
  if self.end <= self.view_min or self.begin >= self.view_max:
@@ -1007,23 +1009,16 @@ class UtterancePGTextItem(pg.TextItem):
1007
1009
  or self.view_max >= self.end > self.view_min
1008
1010
  or (self.begin <= self.view_min and self.end >= self.view_max)
1009
1011
  ):
1012
+ self.update_pos()
1010
1013
  self.show()
1011
1014
 
1012
- def boundingRect(self):
1013
- br = QtCore.QRectF(self.viewRect()) # bounds of containing ViewBox mapped to local coords.
1014
- vb = self.getViewBox()
1015
- if self.begin is None or self.view_min is None:
1016
- return br
1015
+ def update_pos(self):
1017
1016
  visible_begin = max(self.begin, self.view_min)
1018
1017
  visible_end = min(self.end, self.view_max)
1019
-
1020
- br.setLeft(visible_begin)
1021
- br.setRight(visible_end)
1022
-
1023
- br.setTop(self.top_point)
1024
- # br.setBottom(self.top_point-self.per_tier_range)
1025
- br.setBottom(self.bottom_point)
1026
1018
  duration = visible_end - visible_begin
1019
+ vb = self.getViewBox()
1020
+ if vb is None:
1021
+ return
1027
1022
  self._cached_pixel_size = vb.viewPixelSize()
1028
1023
  x_margin_px = 25
1029
1024
  y_margin_top_px = 25
@@ -1038,6 +1033,20 @@ class UtterancePGTextItem(pg.TextItem):
1038
1033
  self.textItem.setGeometry(0, 0, width, height)
1039
1034
  self.text_edit.setFixedWidth(width)
1040
1035
  self.text_edit.setFixedHeight(height)
1036
+
1037
+ @profile
1038
+ def boundingRect(self):
1039
+ br = QtCore.QRectF() # bounds of containing ViewBox mapped to local coords.
1040
+ if self._cached_pixel_size is None:
1041
+ self.update_pos()
1042
+ visible_begin = max(self.begin, self.view_min)
1043
+ visible_end = min(self.end, self.view_max)
1044
+
1045
+ br.setLeft(visible_begin)
1046
+ br.setRight(visible_end)
1047
+
1048
+ br.setTop(self.top_point)
1049
+ br.setBottom(self.bottom_point)
1041
1050
  return br
1042
1051
 
1043
1052
 
@@ -1093,9 +1102,10 @@ class TranscriberErrorHighlighter(QtGui.QSyntaxHighlighter):
1093
1102
  super().__init__(*args)
1094
1103
  self.alignment = None
1095
1104
  self.settings = AnchorSettings()
1105
+ self.plot_theme = self.settings.plot_theme
1096
1106
 
1097
- self.keyword_color = self.settings.plot_theme.error_color
1098
- self.keyword_text_color = self.settings.plot_theme.error_text_color
1107
+ self.keyword_color = self.plot_theme.error_color
1108
+ self.keyword_text_color = self.plot_theme.error_text_color
1099
1109
  self.highlight_format = QtGui.QTextCharFormat()
1100
1110
  self.highlight_format.setBackground(self.keyword_color)
1101
1111
  self.highlight_format.setForeground(self.keyword_text_color)
@@ -1114,16 +1124,15 @@ class TranscriberErrorHighlighter(QtGui.QSyntaxHighlighter):
1114
1124
  return
1115
1125
  current_align_ind = 0
1116
1126
  for word_object in re.finditer(self.WORDS, text):
1117
- while self.alignment.seqB[current_align_ind] != word_object.group():
1118
- current_align_ind += 1
1119
1127
  sb = self.alignment.seqB[current_align_ind]
1120
1128
  sa = self.alignment.seqA[current_align_ind]
1121
- if sb == word_object.group() and sb != sa:
1122
- self.setFormat(
1123
- word_object.start(),
1124
- word_object.end() - word_object.start(),
1125
- self.highlight_format,
1126
- )
1129
+ if sb == word_object.group():
1130
+ if sb != sa:
1131
+ self.setFormat(
1132
+ word_object.start(),
1133
+ word_object.end() - word_object.start(),
1134
+ self.highlight_format,
1135
+ )
1127
1136
  current_align_ind += 1
1128
1137
  if self.search_term:
1129
1138
  if not self.search_term.case_sensitive:
@@ -1133,8 +1142,8 @@ class TranscriberErrorHighlighter(QtGui.QSyntaxHighlighter):
1133
1142
  for i in range(word_object.start(), word_object.end()):
1134
1143
  f = self.format(i)
1135
1144
  f.setFontWeight(QtGui.QFont.Weight.Bold)
1136
- f.setBackground(QtGui.QColor(self.settings.accent_base_color))
1137
- f.setForeground(QtGui.QColor(self.settings.primary_very_dark_color))
1145
+ f.setBackground(QtGui.QColor(self.plot_theme.break_line_color))
1146
+ f.setForeground(QtGui.QColor(self.plot_theme.background_color))
1138
1147
  self.setFormat(i, 1, f)
1139
1148
 
1140
1149
 
@@ -1212,8 +1221,8 @@ class IntervalTextRegion(pg.GraphicsObject):
1212
1221
  self.speaker_id = speaker_id
1213
1222
  super().__init__()
1214
1223
  text = interval.label
1215
- self.text = TextItem(text, color=color, anchor=(0.5, 0.5))
1216
- self.text.setParentItem(self)
1224
+ self.text_item = TextItem(text, color=color, anchor=(0.5, 0.5))
1225
+ self.text_item.setParentItem(self)
1217
1226
 
1218
1227
  self.picture = QtGui.QPicture()
1219
1228
  self.interval = interval
@@ -1225,7 +1234,7 @@ class IntervalTextRegion(pg.GraphicsObject):
1225
1234
  self.mouseHovering = False
1226
1235
  self.selected = False
1227
1236
  self.currentBrush = self.background_brush
1228
- self.text.setPos(
1237
+ self.text_item.setPos(
1229
1238
  (self.interval.begin + self.interval.end) / 2, self.top_point - (self.height / 2)
1230
1239
  )
1231
1240
  self.begin_line = pg.InfiniteLine()
@@ -1271,8 +1280,6 @@ class IntervalTextRegion(pg.GraphicsObject):
1271
1280
 
1272
1281
 
1273
1282
  class TextAttributeRegion(pg.GraphicsObject):
1274
- audioSelected = QtCore.Signal(object, object)
1275
-
1276
1283
  def __init__(
1277
1284
  self,
1278
1285
  parent,
@@ -1281,16 +1288,19 @@ class TextAttributeRegion(pg.GraphicsObject):
1281
1288
  text: str,
1282
1289
  top_point,
1283
1290
  height,
1284
- selection_model: CorpusSelectionModel,
1285
- border=None,
1291
+ selection_model: FileSelectionModel,
1286
1292
  dictionary_model=None,
1287
1293
  speaker_id=None,
1294
+ plot_theme=None,
1288
1295
  ):
1289
1296
  super().__init__()
1290
1297
  self.begin = begin
1291
1298
  self.end = end
1292
1299
  self.text = text
1293
- self.border = border
1300
+ self.plot_theme = plot_theme
1301
+ self.settings = AnchorSettings()
1302
+ if self.plot_theme is None:
1303
+ self.plot_theme = self.settings.plot_theme
1294
1304
  self.dictionary_model = dictionary_model
1295
1305
  self.speaker_id = speaker_id
1296
1306
  self.selection_model = selection_model
@@ -1301,7 +1311,8 @@ class TextAttributeRegion(pg.GraphicsObject):
1301
1311
  self.height = height
1302
1312
  self.bottom_point = self.top_point - self.height
1303
1313
  self.setParentItem(parent)
1304
- self.text = UtterancePGTextItem(
1314
+
1315
+ self.text_item = UtterancePGTextItem(
1305
1316
  self.begin,
1306
1317
  self.end,
1307
1318
  self.text,
@@ -1314,8 +1325,8 @@ class TextAttributeRegion(pg.GraphicsObject):
1314
1325
  speaker_id=self.speaker_id,
1315
1326
  border=pg.mkPen(self.parentItem().settings.accent_light_color),
1316
1327
  )
1317
- self.text.setParentItem(self)
1318
- self.text_edit = self.text.text_edit
1328
+ self.text_item.setParentItem(self)
1329
+ self.text_edit = self.text_item.text_edit
1319
1330
  self.text_edit.setReadOnly(True)
1320
1331
  self.text_edit.setViewportMargins(
1321
1332
  self.parentItem().text_margin_pixels,
@@ -1325,87 +1336,55 @@ class TextAttributeRegion(pg.GraphicsObject):
1325
1336
  )
1326
1337
  self.text_edit.setStyleSheet(self.parentItem().settings.interval_style_sheet)
1327
1338
 
1328
- self.picture = QtGui.QPicture()
1329
- self.mouseHovering = False
1330
1339
  self.selected = False
1331
- self.background_brush = self.parentItem().background_brush
1332
- self.selected_brush = self.parentItem().selected_brush
1333
1340
  self.currentBrush = self.parentItem().currentBrush
1334
- self.text.setPos((self.begin + self.end) / 2, self.top_point - (self.height / 2))
1335
- self.begin_line = pg.InfiniteLine()
1336
- self.rect = QtCore.QRectF(
1337
- left=self.begin,
1338
- top=self.top_point,
1339
- width=self.end - self.begin,
1340
- height=self.height,
1341
- )
1342
- self.rect.setTop(self.top_point)
1343
- self.rect.setBottom(self.bottom_point)
1344
- self._generate_picture()
1345
-
1346
- def setSelected(self, selected):
1347
- if selected:
1348
- new_brush = self.selected_brush
1349
- else:
1350
- new_brush = self.background_brush
1351
- if new_brush != self.currentBrush:
1352
- self.currentBrush = new_brush
1353
- self._generate_picture()
1354
-
1355
- def _generate_picture(self):
1356
- painter = QtGui.QPainter(self.picture)
1357
- painter.setPen(self.border)
1358
- painter.setBrush(self.currentBrush)
1359
- painter.drawRect(self.rect)
1360
- painter.end()
1361
-
1362
- def mouseClickEvent(self, ev):
1363
- if ev.button() != QtCore.Qt.MouseButton.LeftButton:
1364
- ev.ignore()
1365
- return
1366
- self.audioSelected.emit(self.begin, self.end)
1367
- ev.accept()
1341
+ self.text_item.setPos((self.begin + self.end) / 2, self.top_point - (self.height / 2))
1342
+ self._cached_pixel_size = None
1343
+ self.cached_bounds = None
1368
1344
 
1369
1345
  def boundingRect(self):
1370
- br = QtCore.QRectF(self.picture.boundingRect())
1346
+ visible_begin = max(self.begin, self.selection_model.plot_min)
1347
+ visible_end = min(self.end, self.selection_model.plot_max)
1348
+ br = QtCore.QRectF(
1349
+ visible_begin,
1350
+ self.bottom_point,
1351
+ visible_end - visible_begin,
1352
+ abs(self.top_point - self.bottom_point),
1353
+ )
1371
1354
  return br
1372
1355
 
1373
- def paint(self, painter: QtGui.QPainter, *args):
1374
- painter.drawPicture(0, 0, self.picture)
1356
+ def paint(self, painter, option, widget=...):
1357
+ return
1375
1358
 
1376
1359
 
1377
1360
  class TranscriberTextRegion(TextAttributeRegion):
1378
- viewRequested = QtCore.Signal(object, object)
1379
- audioSelected = QtCore.Signal(object, object)
1380
-
1361
+ @profile
1381
1362
  def __init__(
1382
1363
  self,
1383
1364
  parent,
1384
- begin: float,
1385
- end: float,
1386
- text: str,
1365
+ item,
1387
1366
  top_point,
1388
1367
  height,
1389
1368
  selection_model: CorpusSelectionModel,
1390
- border=None,
1391
1369
  dictionary_model=None,
1392
1370
  speaker_id=None,
1393
1371
  alignment=None,
1394
1372
  search_term=None,
1373
+ plot_theme=None,
1395
1374
  ):
1396
1375
  super().__init__(
1397
1376
  parent,
1398
- begin,
1399
- end,
1400
- text,
1377
+ item.begin,
1378
+ item.end,
1379
+ item.transcription_text,
1401
1380
  top_point,
1402
1381
  height,
1403
1382
  selection_model,
1404
- border,
1405
1383
  dictionary_model,
1406
1384
  speaker_id,
1385
+ plot_theme,
1407
1386
  )
1408
-
1387
+ self.item = item
1409
1388
  self.highlighter = TranscriberErrorHighlighter(self.text_edit.document())
1410
1389
  if alignment is not None:
1411
1390
  self.highlighter.set_alignment(alignment)
@@ -1414,9 +1393,6 @@ class TranscriberTextRegion(TextAttributeRegion):
1414
1393
 
1415
1394
 
1416
1395
  class NormalizedTextRegion(TextAttributeRegion):
1417
- viewRequested = QtCore.Signal(object, object)
1418
- audioSelected = QtCore.Signal(object, object)
1419
-
1420
1396
  def __init__(
1421
1397
  self,
1422
1398
  parent,
@@ -1426,10 +1402,10 @@ class NormalizedTextRegion(TextAttributeRegion):
1426
1402
  top_point,
1427
1403
  height,
1428
1404
  selection_model: CorpusSelectionModel,
1429
- border=None,
1430
1405
  dictionary_model=None,
1431
1406
  search_term=None,
1432
1407
  speaker_id=None,
1408
+ plot_theme=None,
1433
1409
  ):
1434
1410
  super().__init__(
1435
1411
  parent,
@@ -1439,17 +1415,21 @@ class NormalizedTextRegion(TextAttributeRegion):
1439
1415
  top_point,
1440
1416
  height,
1441
1417
  selection_model,
1442
- border,
1443
1418
  dictionary_model,
1444
1419
  speaker_id,
1420
+ plot_theme,
1445
1421
  )
1446
1422
 
1447
- self.highlighter = Highlighter(self.text_edit.document())
1423
+ self.highlighter = Highlighter(self.text_item.text_edit.document())
1448
1424
  self.highlighter.set_models(dictionary_model)
1449
1425
  self.highlighter.set_speaker(speaker_id)
1450
1426
  if search_term:
1451
1427
  self.highlighter.setSearchTerm(search_term)
1452
1428
 
1429
+ def boundingRect(self):
1430
+ br = super().boundingRect()
1431
+ return br
1432
+
1453
1433
 
1454
1434
  class Highlighter(QtGui.QSyntaxHighlighter):
1455
1435
  WORDS = rf"[^\s{''.join(DEFAULT_WORD_BREAK_MARKERS)+''.join(DEFAULT_PUNCTUATION)}]+"
@@ -1542,6 +1522,7 @@ class MfaRegion(pg.LinearRegionItem):
1542
1522
  self.item = item
1543
1523
 
1544
1524
  self.settings = AnchorSettings()
1525
+ self.plot_theme = self.settings.plot_theme
1545
1526
 
1546
1527
  self.item_min = self.item.begin
1547
1528
  self.item_max = self.item.end
@@ -1557,13 +1538,13 @@ class MfaRegion(pg.LinearRegionItem):
1557
1538
  self.text_margin_pixels = 2
1558
1539
  self.height = abs(self.top_point - self.bottom_point)
1559
1540
 
1560
- self.interval_background_color = self.settings.plot_theme.interval_background_color
1561
- self.hover_line_color = self.settings.plot_theme.hover_line_color
1562
- self.moving_line_color = self.settings.plot_theme.moving_line_color
1541
+ self.interval_background_color = self.plot_theme.interval_background_color
1542
+ self.hover_line_color = self.plot_theme.hover_line_color
1543
+ self.moving_line_color = self.plot_theme.moving_line_color
1563
1544
 
1564
- self.break_line_color = self.settings.plot_theme.break_line_color
1565
- self.text_color = self.settings.plot_theme.text_color
1566
- self.selected_interval_color = self.settings.plot_theme.selected_interval_color
1545
+ self.break_line_color = self.plot_theme.break_line_color
1546
+ self.text_color = self.plot_theme.text_color
1547
+ self.selected_interval_color = self.plot_theme.selected_interval_color
1567
1548
  self.plot_text_font = self.settings.big_font
1568
1549
  self.setCursor(QtCore.Qt.CursorShape.SizeAllCursor)
1569
1550
  self.pen = pg.mkPen(self.break_line_color, width=3)
@@ -1686,122 +1667,131 @@ class MfaRegion(pg.LinearRegionItem):
1686
1667
  return br
1687
1668
 
1688
1669
 
1689
- class AlignmentRegion(MfaRegion):
1670
+ class IntervalTier(pg.GraphicsObject):
1671
+ highlightRequested = QtCore.Signal(object)
1672
+
1690
1673
  def __init__(
1691
1674
  self,
1692
- phone_interval: CtmInterval,
1693
- corpus_model: CorpusModel,
1694
- file_model: FileUtterancesModel,
1695
- selection_model: CorpusSelectionModel,
1696
- bottom_point: float = 0,
1697
- top_point: float = 1,
1675
+ parent,
1676
+ utterance: CtmInterval,
1677
+ intervals: typing.List[CtmInterval],
1678
+ selection_model: FileSelectionModel,
1679
+ top_point,
1680
+ bottom_point,
1681
+ word=False,
1698
1682
  ):
1699
- super().__init__(
1700
- phone_interval,
1701
- corpus_model,
1702
- file_model,
1703
- None,
1704
- selection_model,
1705
- bottom_point,
1706
- top_point,
1707
- )
1708
- self.original_text = self.item.label
1683
+ super().__init__()
1684
+ self.setParentItem(parent)
1685
+ self.intervals = intervals
1686
+ self.word = word
1687
+ self.array = pg.Qt.internals.PrimitiveArray(QtCore.QRectF, 4)
1688
+ self.array.resize(len(self.intervals))
1689
+ self.settings = AnchorSettings()
1690
+ self.plot_theme = self.settings.plot_theme
1691
+ memory = self.array.ndarray()
1692
+ self.anchor = pg.Point((0.5, 0.5))
1693
+ if self.word:
1694
+ self.plot_text_font = self.settings.font
1695
+ else:
1696
+ self.plot_text_font = self.settings.small_font
1709
1697
 
1710
- self.text = pg.TextItem(
1711
- self.item.label, anchor=(0.5, 0.5), color=self.text_color # , border=pg.mkColor("r")
1712
- )
1713
- self.text.setVisible(False)
1714
-
1715
- self.text.setFont(self.settings.font)
1716
- options = QtGui.QTextOption()
1717
- options.setWrapMode(QtGui.QTextOption.WrapMode.NoWrap)
1718
- self.text.textItem.document().setDefaultTextOption(options)
1719
- self.text.setParentItem(self)
1720
- self.per_tier_range = self.top_point - self.bottom_point
1721
- self.currentBrush = pg.mkBrush((0, 0, 0, 0))
1722
- self.selection_model.viewChanged.connect(self.check_visibility)
1723
- self.check_visibility()
1698
+ fm = QtGui.QFontMetrics(self.plot_text_font)
1699
+ for i, interval in enumerate(intervals):
1700
+ memory[i, 0] = interval.begin
1701
+ memory[i, 2] = interval.end - interval.begin
1702
+ if interval.label not in self.parentItem().painter_path_cache[self.word]:
1703
+ symbol = QtGui.QPainterPath()
1724
1704
 
1725
- def setSelected(self, selected: bool):
1726
- if selected:
1727
- self.text.setColor(self.settings.plot_theme.selected_text_color)
1728
- else:
1729
- self.text.setColor(self.settings.plot_theme.text_color)
1705
+ symbol.addText(0, 0, self.plot_text_font, interval.label)
1706
+ br = symbol.boundingRect()
1730
1707
 
1731
- def check_visibility(self):
1732
- if (self.item_max - self.item_min) / (
1733
- self.selection_model.max_time - self.selection_model.min_time
1734
- ) < 0.001:
1735
- self.hide()
1736
- else:
1737
- vb = self.getViewBox()
1738
- visible_begin = max(self.item_min, self.selection_model.plot_min)
1739
- visible_end = min(self.item_max, self.selection_model.plot_max)
1740
- visible_duration = visible_end - visible_begin
1741
- self.text.setPos(
1742
- visible_begin + (visible_duration / 2), self.top_point - (self.per_tier_range / 2)
1743
- )
1744
- if vb is None:
1745
- self.text.setVisible(
1746
- (visible_end - visible_begin)
1747
- / (self.selection_model.max_time - self.selection_model.min_time)
1748
- > 0.005
1749
- )
1750
- else:
1751
- pixel_size = vb.viewPixelSize()[0]
1752
- if pixel_size != 0:
1753
- x_margin_px = 8
1754
- available_text_width = visible_duration / pixel_size - (2 * x_margin_px)
1708
+ # getting transform object
1709
+ tr = QtGui.QTransform()
1755
1710
 
1756
- self.text.setVisible(available_text_width > 10)
1757
- self.show()
1711
+ # translating
1712
+ tr.translate(-br.x() - br.width() / 2.0, fm.height() / 2.0)
1713
+ self.parentItem().painter_path_cache[self.word][interval.label] = tr.map(symbol)
1758
1714
 
1715
+ memory[:, 1] = bottom_point
1716
+ memory[:, 3] = top_point - bottom_point
1717
+ self.top_point = top_point
1718
+ self.bottom_point = bottom_point
1719
+ self.selection_model = selection_model
1720
+ self.utterance = utterance
1759
1721
 
1760
- class PhoneRegion(AlignmentRegion):
1761
- def __init__(
1762
- self,
1763
- phone_interval: CtmInterval,
1764
- corpus_model: CorpusModel,
1765
- file_model: FileUtterancesModel,
1766
- selection_model: CorpusSelectionModel,
1767
- bottom_point: float = 0,
1768
- top_point: float = 1,
1769
- color=None,
1770
- ):
1771
- super().__init__(
1772
- phone_interval, corpus_model, file_model, selection_model, bottom_point, top_point
1773
- )
1774
- if color is not None:
1775
- self.currentBrush = pg.mkBrush(color)
1776
- self._generate_picture()
1722
+ self.background_color = self.plot_theme.background_color
1723
+ self.hover_line_color = self.plot_theme.hover_line_color
1724
+ self.moving_line_color = self.plot_theme.moving_line_color
1777
1725
 
1726
+ self.break_line_color = self.plot_theme.break_line_color
1727
+ self.text_color = self.plot_theme.text_color
1728
+ self.selected_interval_color = self.plot_theme.selected_interval_color
1729
+ self.text_pen = pg.mkPen(self.text_color)
1730
+ self.border_pen = pg.mkPen(self.break_line_color, width=1)
1731
+ self.border_pen.setCapStyle(QtCore.Qt.PenCapStyle.FlatCap)
1778
1732
 
1779
- class WordRegion(AlignmentRegion):
1780
- highlightRequested = QtCore.Signal(object)
1733
+ def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
1734
+ if e.button() == QtCore.Qt.MouseButton.LeftButton:
1735
+ time = e.pos().x()
1736
+ memory = self.array.ndarray()
1737
+ if memory.shape[0] > 0:
1738
+ index = np.searchsorted(memory[:, 0], time) - 1
1739
+ self.selection_model.select_audio(
1740
+ self.intervals[index].begin, self.intervals[index].end
1741
+ )
1742
+ if self.word:
1743
+ self.highlightRequested.emit(
1744
+ TextFilterQuery(self.intervals[index].label, word=True)
1745
+ )
1746
+ e.accept()
1747
+ return
1781
1748
 
1782
- def __init__(
1783
- self,
1784
- word_interval: CtmInterval,
1785
- corpus_model: CorpusModel,
1786
- file_model: FileUtterancesModel,
1787
- selection_model: CorpusSelectionModel,
1788
- bottom_point: float = 0,
1789
- top_point: float = 1,
1790
- ):
1791
- super().__init__(
1792
- word_interval, corpus_model, file_model, selection_model, bottom_point, top_point
1793
- )
1794
- self._generate_picture()
1749
+ return super().mousePressEvent(e)
1795
1750
 
1796
- def mouseClickEvent(self, ev: QtGui.QMouseEvent):
1797
- search_term = TextFilterQuery(self.item.label, word=True)
1798
- self.highlightRequested.emit(search_term)
1799
- super().mouseClickEvent(ev)
1751
+ def paint(self, painter, *args):
1752
+ inst = self.array.instances()
1753
+ vb = self.getViewBox()
1754
+ px = vb.viewPixelSize()
1755
+ painter.setPen(self.border_pen)
1756
+ painter.drawRects(inst)
1757
+ total_time = self.selection_model.max_time - self.selection_model.min_time
1758
+ for i, interval in enumerate(self.intervals):
1759
+ r = inst[i]
1760
+ visible_begin = max(r.left(), self.selection_model.plot_min)
1761
+ visible_end = min(r.right(), self.selection_model.plot_max)
1762
+ visible_duration = visible_end - visible_begin
1763
+ if visible_duration / total_time <= 0.0075:
1764
+ continue
1765
+ x = (r.left() + r.right()) / 2
1766
+ painter.save()
1767
+ options = QtGui.QTextOption()
1768
+ options.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
1769
+ painter.setRenderHint(painter.RenderHint.Antialiasing, True)
1770
+ painter.setPen(self.text_pen)
1771
+ painter.setBrush(self.text_color)
1772
+ painter.translate(x, (self.top_point + self.bottom_point) / 2)
1773
+ path = self.parentItem().painter_path_cache[self.word][interval.label]
1774
+ painter.scale(px[0], -px[1])
1775
+ painter.drawPath(path)
1776
+ painter.restore()
1777
+
1778
+ def boundingRect(self):
1779
+ return QtCore.QRectF(
1780
+ self.utterance.begin,
1781
+ self.bottom_point,
1782
+ self.utterance.end - self.utterance.begin,
1783
+ abs(self.top_point - self.bottom_point),
1784
+ )
1800
1785
 
1801
1786
 
1802
1787
  class UtteranceRegion(MfaRegion):
1788
+ lookUpWord = QtCore.Signal(object)
1789
+ createWord = QtCore.Signal(object)
1790
+
1791
+ @profile
1803
1792
  def __init__(
1804
1793
  self,
1794
+ parent,
1805
1795
  utterance: workers.UtteranceData,
1806
1796
  corpus_model: CorpusModel,
1807
1797
  file_model: FileUtterancesModel,
@@ -1822,6 +1812,8 @@ class UtteranceRegion(MfaRegion):
1822
1812
  bottom_point,
1823
1813
  top_point,
1824
1814
  )
1815
+ self.setParentItem(parent)
1816
+ plot_theme = self.settings.plot_theme
1825
1817
  self.hide()
1826
1818
  self.item = utterance
1827
1819
  self.selection_model = selection_model
@@ -1874,44 +1866,34 @@ class UtteranceRegion(MfaRegion):
1874
1866
 
1875
1867
  self.corpus_model.utteranceTextUpdated.connect(self.update_text_from_model)
1876
1868
  self.original_text = self.item.text
1877
- self.text = UtterancePGTextItem(
1869
+ self.text_item = NormalizedTextRegion(
1870
+ self,
1878
1871
  self.item.begin,
1879
1872
  self.item.end,
1880
- self.item.text,
1873
+ self.item.normalized_text,
1874
+ self.top_point,
1875
+ self.per_tier_range,
1881
1876
  self.selection_model,
1882
- anchor=(0, 0),
1883
- top_point=self.top_point,
1884
- bottom_point=self.bottom_point,
1885
- per_tier_range=self.per_tier_range,
1886
- dictionary_model=self.dictionary_model,
1887
- speaker_id=self.item.speaker_id,
1888
- border=pg.mkPen(self.settings.plot_theme.break_line_color),
1877
+ dictionary_model=dictionary_model,
1878
+ search_term=search_term,
1879
+ speaker_id=utterance.speaker_id,
1889
1880
  )
1890
- self.text.setParentItem(self)
1891
1881
 
1892
- self.text_edit = self.text.text_edit
1893
- if not self.corpus_model.editable:
1894
- self.text_edit.setReadOnly(True)
1882
+ self.text_edit = self.text_item.text_edit
1883
+ self.text_edit.gainedFocus.connect(self.select_self)
1884
+ self.text_edit.menuRequested.connect(self.generate_text_edit_menu)
1885
+ self.text_edit.setReadOnly(False)
1895
1886
  self.corpus_model.editableChanged.connect(self.change_editing)
1896
- self.text_edit.setViewportMargins(
1897
- self.text_margin_pixels,
1898
- self.text_margin_pixels,
1899
- self.text_margin_pixels,
1900
- self.text_margin_pixels,
1901
- )
1902
- self.text_edit.setStyleSheet(self.settings.interval_style_sheet)
1903
1887
  self.text_edit.installEventFilter(self)
1904
- self.highlighter = Highlighter(self.text_edit.document())
1905
- self.highlighter.set_models(dictionary_model)
1906
- self.highlighter.set_speaker(self.item.speaker_id)
1907
- if search_term:
1908
- self.highlighter.setSearchTerm(search_term)
1909
1888
  self.timer = QtCore.QTimer()
1910
1889
  self.text_edit.textChanged.connect(self.refresh_timer)
1911
1890
  self.text_edit.lostFocus.connect(self.save_changes)
1891
+ self.text_edit.gainedFocus.connect(self.select_self)
1892
+ self.text_edit.menuRequested.connect(self.generate_text_edit_menu)
1912
1893
  self.timer.timeout.connect(self.save_changes)
1913
1894
  self._cached_pixel_size = None
1914
1895
  self.normalized_text = None
1896
+ self.transcription_text = None
1915
1897
  i = -1
1916
1898
  for tier_name, lookup in self.extra_tiers.items():
1917
1899
  if not visible_tiers[tier_name]:
@@ -1929,23 +1911,19 @@ class UtteranceRegion(MfaRegion):
1929
1911
  tier_top_point,
1930
1912
  self.per_tier_range,
1931
1913
  self.selection_model,
1932
- border=pg.mkPen(self.settings.plot_theme.break_line_color),
1933
1914
  dictionary_model=dictionary_model,
1934
1915
  search_term=search_term,
1935
1916
  speaker_id=utterance.speaker_id,
1936
1917
  )
1918
+ self.normalized_text.text_edit.gainedFocus.connect(self.select_self)
1919
+ self.normalized_text.text_edit.menuRequested.connect(self.generate_text_edit_menu)
1937
1920
  continue
1938
- alignment = None
1939
- intervals = getattr(self.item, lookup)
1940
- if lookup in {"transcription_text", "normalized_text"}:
1941
- if (
1942
- lookup == "transcription_text"
1943
- and self.item.text
1944
- and self.item.transcription_text
1945
- ):
1921
+ elif lookup == "transcription_text":
1922
+ alignment = None
1923
+ if self.item.normalized_text and self.item.transcription_text:
1946
1924
  alignment = pairwise2.align.globalms(
1947
- self.item.text.split(),
1948
- self.item.transcription_text.split(),
1925
+ self.item.normalized_text.lower().split(),
1926
+ self.item.transcription_text.lower().split(),
1949
1927
  0,
1950
1928
  -2,
1951
1929
  -1,
@@ -1953,18 +1931,39 @@ class UtteranceRegion(MfaRegion):
1953
1931
  gap_char=["-"],
1954
1932
  one_alignment_only=True,
1955
1933
  )[0]
1934
+ self.transcription_text = TranscriberTextRegion(
1935
+ self,
1936
+ self.item,
1937
+ tier_top_point,
1938
+ self.per_tier_range,
1939
+ self.selection_model,
1940
+ alignment=alignment,
1941
+ dictionary_model=dictionary_model,
1942
+ search_term=search_term,
1943
+ speaker_id=utterance.speaker_id,
1944
+ plot_theme=self.plot_theme,
1945
+ )
1946
+ self.transcription_text.setParentItem(self)
1947
+ self.transcription_text.text_edit.gainedFocus.connect(self.select_self)
1948
+ self.transcription_text.text_edit.menuRequested.connect(
1949
+ self.generate_text_edit_menu
1950
+ )
1951
+ continue
1952
+ intervals = getattr(self.item, lookup)
1956
1953
 
1957
1954
  self.extra_tier_intervals[tier_name] = []
1958
1955
 
1959
1956
  if intervals is None:
1960
1957
  continue
1958
+ if not isinstance(intervals, list):
1959
+ intervals = [intervals]
1961
1960
  min_confidence = None
1962
1961
  max_confidence = None
1963
1962
  cmap = pg.ColorMap(
1964
1963
  None,
1965
1964
  [
1966
- self.settings.plot_theme.error_color,
1967
- self.settings.plot_theme.interval_background_color,
1965
+ plot_theme.error_color,
1966
+ plot_theme.interval_background_color,
1968
1967
  ],
1969
1968
  )
1970
1969
  cmap.linearize()
@@ -1976,64 +1975,49 @@ class UtteranceRegion(MfaRegion):
1976
1975
  min_confidence = interval.confidence
1977
1976
  if max_confidence is None or interval.confidence > max_confidence:
1978
1977
  max_confidence = interval.confidence
1979
- for interval in intervals:
1980
- if lookup == "transcription_text":
1981
- interval_reg = TranscriberTextRegion(
1982
- self,
1983
- self.item.begin,
1984
- self.item.end,
1985
- self.item.transcription_text,
1986
- tier_top_point,
1987
- self.per_tier_range,
1988
- self.selection_model,
1989
- border=pg.mkPen(self.settings.plot_theme.break_line_color),
1990
- alignment=alignment,
1991
- dictionary_model=dictionary_model,
1992
- search_term=search_term,
1993
- speaker_id=utterance.speaker_id,
1978
+ interval_tier = IntervalTier(
1979
+ self,
1980
+ self.item,
1981
+ intervals,
1982
+ self.selection_model,
1983
+ top_point=tier_top_point,
1984
+ bottom_point=tier_bottom_point,
1985
+ word=False,
1986
+ )
1987
+
1988
+ elif "word_intervals" in lookup:
1989
+ interval_tier = IntervalTier(
1990
+ self,
1991
+ self.item,
1992
+ intervals,
1993
+ self.selection_model,
1994
+ top_point=tier_top_point,
1995
+ bottom_point=tier_bottom_point,
1996
+ word=True,
1997
+ )
1998
+ interval_tier.highlightRequested.connect(self.text_item.highlighter.setSearchTerm)
1999
+ if self.transcription_text is not None:
2000
+ interval_tier.highlightRequested.connect(
2001
+ self.transcription_text.highlighter.setSearchTerm
1994
2002
  )
1995
- interval_reg.setParentItem(self)
1996
- interval_reg.audioSelected.connect(self.setSelected)
1997
- elif "phone_intervals" in lookup:
1998
- color = None
1999
- if interval.confidence is not None:
2000
- normalized_confidence = (interval.confidence - min_confidence) / (
2001
- max_confidence - min_confidence
2002
- )
2003
- color = cmap.map(normalized_confidence)
2004
- interval_reg = PhoneRegion(
2005
- interval,
2006
- self.corpus_model,
2007
- self.file_model,
2008
- selection_model=selection_model,
2009
- top_point=tier_top_point,
2010
- bottom_point=tier_bottom_point,
2011
- color=color,
2003
+ if self.normalized_text is not None:
2004
+ interval_tier.highlightRequested.connect(
2005
+ self.normalized_text.highlighter.setSearchTerm
2012
2006
  )
2013
- interval_reg.setParentItem(self)
2014
- interval_reg.audioSelected.connect(self.setSelected)
2015
- elif "word_intervals" in lookup:
2016
- interval_reg = WordRegion(
2017
- interval,
2018
- self.corpus_model,
2019
- self.file_model,
2020
- selection_model=selection_model,
2021
- top_point=tier_top_point,
2022
- bottom_point=tier_bottom_point,
2023
- )
2024
- interval_reg.setParentItem(self)
2025
- interval_reg.highlightRequested.connect(self.highlighter.setSearchTerm)
2026
- interval_reg.audioSelected.connect(self.setSelected)
2027
2007
 
2008
+ for interval in intervals:
2009
+ if "phone_intervals" in lookup or "word_intervals" in lookup:
2010
+ continue
2028
2011
  else:
2029
2012
  interval_reg = IntervalTextRegion(
2030
2013
  interval,
2031
2014
  self.text_color,
2032
- border=pg.mkPen(self.settings.plot_theme.break_line_color, width=3),
2015
+ border=pg.mkPen(plot_theme.break_line_color, width=1),
2033
2016
  top_point=tier_top_point,
2034
2017
  height=self.per_tier_range,
2035
2018
  background_brush=self.background_brush,
2036
2019
  selected_brush=pg.mkBrush(self.selected_interval_color),
2020
+ plot_theme=self.plot_theme,
2037
2021
  )
2038
2022
  interval_reg.audioSelected.connect(self.setSelected)
2039
2023
  interval_reg.setParentItem(self)
@@ -2042,9 +2026,23 @@ class UtteranceRegion(MfaRegion):
2042
2026
  interval_reg.viewRequested.connect(self.viewRequested.emit)
2043
2027
  self.extra_tier_intervals[tier_name].append(interval_reg)
2044
2028
  self.selection_model.viewChanged.connect(self.update_view_times)
2029
+ self.lookUpWord.connect(self.dictionary_model.lookup_word)
2030
+ self.createWord.connect(self.dictionary_model.add_word)
2045
2031
  self.show()
2046
2032
  self.available_speakers = available_speakers
2047
2033
 
2034
+ @property
2035
+ def painter_path_cache(self):
2036
+ return self.parentItem().painter_path_cache
2037
+
2038
+ def update_edit_fields(self):
2039
+ begin, end = self.getRegion()
2040
+ self.text_item.text_item.update_times(begin, end)
2041
+ if self.normalized_text is not None:
2042
+ self.normalized_text.text_item.update_times(begin, end)
2043
+ if self.transcription_text is not None:
2044
+ self.transcription_text.text_item.update_times(begin, end)
2045
+
2048
2046
  def show(self):
2049
2047
  for intervals in self.extra_tier_intervals.values():
2050
2048
  for interval in intervals:
@@ -2061,11 +2059,6 @@ class UtteranceRegion(MfaRegion):
2061
2059
  interval.hide()
2062
2060
  super().show()
2063
2061
 
2064
- def setSelected(self, selected: bool):
2065
- if selected:
2066
- self.text_edit.setFocus()
2067
- super().setSelected(selected)
2068
-
2069
2062
  def change_editing(self, editable: bool):
2070
2063
  self.lines[0].movable = editable
2071
2064
  self.lines[1].movable = editable
@@ -2085,7 +2078,7 @@ class UtteranceRegion(MfaRegion):
2085
2078
  self.movable = False
2086
2079
  self.setAcceptHoverEvents(False)
2087
2080
 
2088
- def contextMenuEvent(self, ev: QtWidgets.QGraphicsSceneContextMenuEvent):
2081
+ def construct_context_menu(self):
2089
2082
  menu = QtWidgets.QMenu()
2090
2083
  change_speaker_menu = QtWidgets.QMenu("Change speaker")
2091
2084
  a = QtGui.QAction(menu)
@@ -2130,6 +2123,26 @@ class UtteranceRegion(MfaRegion):
2130
2123
  change_speaker_menu.setStyleSheet(self.settings.menu_style_sheet)
2131
2124
  visible_tiers_menu.setStyleSheet(self.settings.menu_style_sheet)
2132
2125
  menu.setStyleSheet(self.settings.menu_style_sheet)
2126
+ return menu
2127
+
2128
+ def generate_text_edit_menu(self, word, location):
2129
+ menu = self.construct_context_menu()
2130
+ menu.addSeparator()
2131
+ if self.dictionary_model.check_word(word, speaker_id=self.item.speaker_id):
2132
+ lookUpAction = QtGui.QAction(f'Look up "{word}" in dictionary', self)
2133
+ lookUpAction.triggered.connect(lambda: self.lookUpWord.emit(word))
2134
+ lookUpAction.triggered.connect(menu.hide)
2135
+ menu.addAction(lookUpAction)
2136
+ else:
2137
+ createAction = QtGui.QAction(f'Add pronunciation for "{word}"', self)
2138
+ createAction.triggered.connect(lambda: self.createWord.emit(word))
2139
+ createAction.triggered.connect(menu.hide)
2140
+ menu.addAction(createAction)
2141
+ menu.setStyleSheet(self.settings.menu_style_sheet)
2142
+ menu.exec_(location)
2143
+
2144
+ def contextMenuEvent(self, ev: QtWidgets.QGraphicsSceneContextMenuEvent):
2145
+ menu = self.construct_context_menu()
2133
2146
  menu.exec_(ev.screenPos())
2134
2147
 
2135
2148
  def update_tier_visibility(self, checked):
@@ -2282,7 +2295,7 @@ class UtteranceRegion(MfaRegion):
2282
2295
  if utterance_id != self.item.id or new_text == self.original_text:
2283
2296
  return
2284
2297
  self.original_text = new_text
2285
- with QtCore.QSignalBlocker(self.text.text_edit):
2298
+ with QtCore.QSignalBlocker(self.text_item.text_edit):
2286
2299
  position = self.text_edit.textCursor().position()
2287
2300
  end_offset = self.text_edit.document().characterCount() - position
2288
2301
  self.text_edit.setPlainText(new_text)
@@ -2332,10 +2345,11 @@ class WaveForm(pg.PlotCurveItem):
2332
2345
  class PitchTrack(pg.PlotCurveItem):
2333
2346
  def __init__(self, bottom_point, top_point):
2334
2347
  self.settings = AnchorSettings()
2348
+ self.plot_theme = self.settings.plot_theme
2335
2349
  self.top_point = top_point
2336
2350
  self.bottom_point = bottom_point
2337
2351
  self.mid_point = (self.top_point + self.bottom_point) / 2
2338
- pen = pg.mkPen(self.settings.plot_theme.pitch_color, width=3)
2352
+ pen = pg.mkPen(self.plot_theme.pitch_color, width=3)
2339
2353
  super().__init__()
2340
2354
  self.setPen(pen)
2341
2355
  self.channel = 0
@@ -2344,24 +2358,24 @@ class PitchTrack(pg.PlotCurveItem):
2344
2358
  self.setAcceptHoverEvents(False)
2345
2359
  self.min_label = pg.TextItem(
2346
2360
  str(self.settings.PITCH_MIN_F0),
2347
- self.settings.plot_theme.pitch_color,
2361
+ self.plot_theme.pitch_color,
2348
2362
  anchor=(1, 1),
2349
2363
  )
2350
2364
  self.min_label.setFont(self.settings.font)
2351
2365
  self.min_label.setParentItem(self)
2352
2366
  self.max_label = pg.TextItem(
2353
2367
  str(self.settings.PITCH_MAX_F0),
2354
- self.settings.plot_theme.pitch_color,
2368
+ self.plot_theme.pitch_color,
2355
2369
  anchor=(1, 0),
2356
2370
  )
2357
2371
  self.max_label.setFont(self.settings.font)
2358
2372
  self.max_label.setParentItem(self)
2359
2373
 
2360
2374
  def update_theme(self):
2361
- pen = pg.mkPen(self.settings.plot_theme.pitch_color, width=3)
2375
+ pen = pg.mkPen(self.plot_theme.pitch_color, width=3)
2362
2376
  self.setPen(pen)
2363
- self.min_label.setColor(self.settings.plot_theme.pitch_color)
2364
- self.max_label.setColor(self.settings.plot_theme.pitch_color)
2377
+ self.min_label.setColor(self.plot_theme.pitch_color)
2378
+ self.max_label.setColor(self.plot_theme.pitch_color)
2365
2379
 
2366
2380
  def hoverEvent(self, ev):
2367
2381
  return
@@ -2379,6 +2393,7 @@ class PitchTrack(pg.PlotCurveItem):
2379
2393
  class Spectrogram(pg.ImageItem):
2380
2394
  def __init__(self, bottom_point, top_point):
2381
2395
  self.settings = AnchorSettings()
2396
+ self.plot_theme = self.settings.plot_theme
2382
2397
  self.top_point = top_point
2383
2398
  self.bottom_point = bottom_point
2384
2399
  self.selection_model = None
@@ -2387,8 +2402,8 @@ class Spectrogram(pg.ImageItem):
2387
2402
  self.cmap = pg.ColorMap(
2388
2403
  None,
2389
2404
  [
2390
- self.settings.plot_theme.background_color,
2391
- self.settings.plot_theme.spectrogram_color,
2405
+ self.plot_theme.background_color,
2406
+ self.plot_theme.spectrogram_color,
2392
2407
  ],
2393
2408
  )
2394
2409
  self.cmap.linearize()
@@ -2404,8 +2419,8 @@ class Spectrogram(pg.ImageItem):
2404
2419
  self.cmap = pg.ColorMap(
2405
2420
  None,
2406
2421
  [
2407
- self.settings.plot_theme.background_color,
2408
- self.settings.plot_theme.spectrogram_color,
2422
+ self.plot_theme.background_color,
2423
+ self.plot_theme.spectrogram_color,
2409
2424
  ],
2410
2425
  )
2411
2426
  self.cmap.linearize()
@@ -2487,6 +2502,7 @@ class AudioPlots(pg.GraphicsObject):
2487
2502
  def __init__(self, top_point, separator_point, bottom_point):
2488
2503
  super().__init__()
2489
2504
  self.settings = AnchorSettings()
2505
+ self.plot_theme = self.settings.plot_theme
2490
2506
  self.selection_model: typing.Optional[FileSelectionModel] = None
2491
2507
  self.top_point = top_point
2492
2508
  self.separator_point = separator_point
@@ -2497,20 +2513,24 @@ class AudioPlots(pg.GraphicsObject):
2497
2513
  self.wave_form.setParentItem(self)
2498
2514
  self.spectrogram.setParentItem(self)
2499
2515
  self.pitch_track.setParentItem(self)
2500
- color = self.settings.plot_theme.selected_range_color
2516
+ color = self.plot_theme.selected_range_color
2501
2517
  color.setAlphaF(0.25)
2502
2518
  self.selection_brush = pg.mkBrush(color)
2503
- self.background_pen = pg.mkPen(self.settings.plot_theme.break_line_color)
2504
- self.background_brush = pg.mkBrush(self.settings.plot_theme.background_color)
2519
+ self.background_pen = pg.mkPen(self.plot_theme.background_color)
2520
+ self.background_brush = pg.mkBrush(self.plot_theme.background_color)
2505
2521
  self.selection_area = SelectionArea(
2506
2522
  top_point=self.top_point,
2507
2523
  bottom_point=self.bottom_point,
2508
2524
  brush=self.selection_brush,
2509
2525
  clipItem=self,
2510
- pen=pg.mkPen(self.settings.plot_theme.selected_interval_color),
2526
+ pen=pg.mkPen(self.plot_theme.selected_interval_color),
2511
2527
  )
2512
2528
  self.selection_area.setParentItem(self)
2513
2529
 
2530
+ self.play_timer = QtCore.QTimer()
2531
+ self.play_timer.setInterval(1)
2532
+ self.play_timer.timeout.connect(self.update_play_line)
2533
+
2514
2534
  self.play_line = pg.InfiniteLine(
2515
2535
  pos=-20,
2516
2536
  span=(0, 1),
@@ -2523,7 +2543,7 @@ class AudioPlots(pg.GraphicsObject):
2523
2543
  pos=-20,
2524
2544
  span=(0, 1),
2525
2545
  pen=pg.mkPen(
2526
- self.settings.plot_theme.selected_interval_color,
2546
+ self.plot_theme.selected_interval_color,
2527
2547
  width=3,
2528
2548
  style=QtCore.Qt.PenStyle.DashLine,
2529
2549
  ),
@@ -2649,12 +2669,17 @@ class AudioPlots(pg.GraphicsObject):
2649
2669
  br = QtCore.QRectF(self.picture.boundingRect())
2650
2670
  return br
2651
2671
 
2652
- def update_play_line(self, time):
2672
+ def update_play_line(self, time=None):
2653
2673
  if time is None:
2654
2674
  return
2675
+ self.play_line.setVisible(
2676
+ self.selection_model.min_time <= time <= self.selection_model.max_time
2677
+ )
2655
2678
  self.play_line.setPos(time)
2656
2679
 
2657
2680
  def update_plot(self):
2681
+ self.setVisible(False)
2682
+ self.play_line.setVisible(False)
2658
2683
  if (
2659
2684
  self.selection_model.model().file is None
2660
2685
  or self.selection_model.model().file.sound_file is None
@@ -2664,9 +2689,8 @@ class AudioPlots(pg.GraphicsObject):
2664
2689
  self.rect.setLeft(self.selection_model.plot_min)
2665
2690
  self.rect.setRight(self.selection_model.plot_max)
2666
2691
  self._generate_picture()
2667
- self.update_play_line(self.selection_model.plot_min)
2668
- self.selection_area.update_region()
2669
- self.update()
2692
+ # self.selection_area.update_region()
2693
+ self.setVisible(True)
2670
2694
 
2671
2695
 
2672
2696
  class SpeakerTier(pg.GraphicsObject):
@@ -2692,14 +2716,13 @@ class SpeakerTier(pg.GraphicsObject):
2692
2716
  self.selection_model = selection_model
2693
2717
  self.dictionary_model = dictionary_model
2694
2718
  self.settings = AnchorSettings()
2719
+ self.plot_theme = self.settings.plot_theme
2695
2720
  self.search_term = search_term
2696
2721
  self.speaker_id = speaker_id
2697
2722
  self.speaker_name = speaker_name
2698
2723
  self.speaker_index = 0
2699
2724
  self.top_point = top_point
2700
- self.speaker_label = pg.TextItem(
2701
- self.speaker_name, color=self.settings.plot_theme.break_line_color
2702
- )
2725
+ self.speaker_label = pg.TextItem(self.speaker_name, color=self.plot_theme.break_line_color)
2703
2726
  self.speaker_label.setFont(self.settings.font)
2704
2727
  self.speaker_label.setParentItem(self)
2705
2728
  self.speaker_label.setZValue(40)
@@ -2707,8 +2730,8 @@ class SpeakerTier(pg.GraphicsObject):
2707
2730
  self.annotation_range = self.top_point - self.bottom_point
2708
2731
  self.extra_tiers = {}
2709
2732
  self.visible_utterances: dict[str, UtteranceRegion] = {}
2710
- self.background_brush = pg.mkBrush(self.settings.plot_theme.background_color)
2711
- self.border = pg.mkPen(self.settings.plot_theme.break_line_color)
2733
+ self.background_brush = pg.mkBrush(self.plot_theme.background_color)
2734
+ self.border = pg.mkPen(self.plot_theme.break_line_color)
2712
2735
  self.picture = QtGui.QPicture()
2713
2736
  self.has_visible_utterances = False
2714
2737
  self.has_selected_utterances = False
@@ -2724,6 +2747,7 @@ class SpeakerTier(pg.GraphicsObject):
2724
2747
  self.selection_model.selectionChanged.connect(self.update_select)
2725
2748
  self.selection_model.model().utterancesReady.connect(self.refresh)
2726
2749
  self.available_speakers = {}
2750
+ self.painter_path_cache = {True: {}, False: {}}
2727
2751
 
2728
2752
  def wheelEvent(self, ev):
2729
2753
  self.receivedWheelEvent.emit(ev)
@@ -2738,6 +2762,10 @@ class SpeakerTier(pg.GraphicsObject):
2738
2762
  p.drawPicture(0, 0, self.picture)
2739
2763
 
2740
2764
  def _generate_picture(self):
2765
+ speaker_name = self.corpus_model.get_speaker_name(self.speaker_id)
2766
+ if speaker_name != self.speaker_name:
2767
+ self.speaker_name = speaker_name
2768
+ self.speaker_label.setText(self.speaker_name)
2741
2769
  self.picture = QtGui.QPicture()
2742
2770
  painter = QtGui.QPainter(self.picture)
2743
2771
  painter.setPen(self.border)
@@ -2762,7 +2790,7 @@ class SpeakerTier(pg.GraphicsObject):
2762
2790
 
2763
2791
  def setSearchTerm(self, term):
2764
2792
  for utt in self.visible_utterances.values():
2765
- utt.highlighter.setSearchTerm(term)
2793
+ utt.text_item.highlighter.setSearchTerm(term)
2766
2794
 
2767
2795
  def refreshTexts(self, utt_id, text):
2768
2796
  for reg in self.visible_utterances.values():
@@ -2778,6 +2806,7 @@ class SpeakerTier(pg.GraphicsObject):
2778
2806
  reg.scene().removeItem(reg)
2779
2807
  self.visible_utterances = {}
2780
2808
 
2809
+ @profile
2781
2810
  def refresh(self, *args):
2782
2811
  self.hide()
2783
2812
  if self.selection_model.plot_min is None:
@@ -2822,6 +2851,7 @@ class SpeakerTier(pg.GraphicsObject):
2822
2851
  self.has_visible_utterances = True
2823
2852
  # Utterance region always at the top
2824
2853
  reg = UtteranceRegion(
2854
+ self,
2825
2855
  u,
2826
2856
  self.corpus_model,
2827
2857
  self.file_model,
@@ -2847,7 +2877,6 @@ class SpeakerTier(pg.GraphicsObject):
2847
2877
  reg.viewRequested.connect(self.selection_model.set_view_times)
2848
2878
  reg.textEdited.connect(self.update_utterance_text)
2849
2879
  reg.selectRequested.connect(self.selection_model.update_select)
2850
- reg.setParentItem(self)
2851
2880
  self.visible_utterances[u.id] = reg
2852
2881
 
2853
2882
  self.show()
@@ -2897,13 +2926,7 @@ class SpeakerTier(pg.GraphicsObject):
2897
2926
  if other_begin < end <= other_end or end > other_begin > other_end > beg:
2898
2927
  reg.setRegion([beg, other_begin])
2899
2928
  break
2900
- reg.text.begin, reg.text.end = reg.getRegion()
2901
- reg.text.update_times(self.selection_model.plot_min, self.selection_model.plot_max)
2902
- if reg.normalized_text is not None:
2903
- reg.normalized_text.text.begin, reg.normalized_text.text.end = reg.getRegion()
2904
- reg.normalized_text.text.update_times(
2905
- self.selection_model.plot_min, self.selection_model.plot_max
2906
- )
2929
+ reg.update_edit_fields()
2907
2930
  reg.select_self()
2908
2931
  reg.update()
2909
2932
 
@@ -2918,7 +2941,5 @@ class SpeakerTier(pg.GraphicsObject):
2918
2941
  return
2919
2942
  self.selection_model.model().update_utterance_times(utt, begin=new_begin, end=new_end)
2920
2943
  self.selection_model.request_start_time(new_begin)
2921
- reg.text.begin = new_begin
2922
- reg.text.end = new_end
2923
- reg.update()
2944
+ reg.update_edit_fields()
2924
2945
  self.lineDragFinished.emit(True)