Anchor-annotator 0.6.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,82 +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
1340
  self.currentBrush = self.parentItem().currentBrush
1332
- self.text.setPos((self.begin + self.end) / 2, self.top_point - (self.height / 2))
1333
- self.begin_line = pg.InfiniteLine()
1334
- self.rect = QtCore.QRectF(
1335
- left=self.begin,
1336
- top=self.top_point,
1337
- width=self.end - self.begin,
1338
- height=self.height,
1339
- )
1340
- self.rect.setTop(self.top_point)
1341
- self.rect.setBottom(self.bottom_point)
1342
- self._generate_picture()
1343
-
1344
- def setSelected(self, selected):
1345
- new_brush = self.parentItem().currentBrush
1346
- if new_brush != self.currentBrush:
1347
- self.currentBrush = new_brush
1348
- self._generate_picture()
1349
-
1350
- def _generate_picture(self):
1351
- painter = QtGui.QPainter(self.picture)
1352
- painter.setPen(self.border)
1353
- painter.setBrush(self.currentBrush)
1354
- painter.drawRect(self.rect)
1355
- painter.end()
1356
-
1357
- def mouseClickEvent(self, ev):
1358
- if ev.button() != QtCore.Qt.MouseButton.LeftButton:
1359
- ev.ignore()
1360
- return
1361
- self.audioSelected.emit(self.begin, self.end)
1362
- 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
1363
1344
 
1364
1345
  def boundingRect(self):
1365
- 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
+ )
1366
1354
  return br
1367
1355
 
1368
- def paint(self, painter: QtGui.QPainter, *args):
1369
- painter.drawPicture(0, 0, self.picture)
1356
+ def paint(self, painter, option, widget=...):
1357
+ return
1370
1358
 
1371
1359
 
1372
1360
  class TranscriberTextRegion(TextAttributeRegion):
1373
- viewRequested = QtCore.Signal(object, object)
1374
- audioSelected = QtCore.Signal(object, object)
1375
-
1361
+ @profile
1376
1362
  def __init__(
1377
1363
  self,
1378
1364
  parent,
1379
- begin: float,
1380
- end: float,
1381
- text: str,
1365
+ item,
1382
1366
  top_point,
1383
1367
  height,
1384
1368
  selection_model: CorpusSelectionModel,
1385
- border=None,
1386
1369
  dictionary_model=None,
1387
1370
  speaker_id=None,
1388
1371
  alignment=None,
1389
1372
  search_term=None,
1373
+ plot_theme=None,
1390
1374
  ):
1391
1375
  super().__init__(
1392
1376
  parent,
1393
- begin,
1394
- end,
1395
- text,
1377
+ item.begin,
1378
+ item.end,
1379
+ item.transcription_text,
1396
1380
  top_point,
1397
1381
  height,
1398
1382
  selection_model,
1399
- border,
1400
1383
  dictionary_model,
1401
1384
  speaker_id,
1385
+ plot_theme,
1402
1386
  )
1403
-
1387
+ self.item = item
1404
1388
  self.highlighter = TranscriberErrorHighlighter(self.text_edit.document())
1405
1389
  if alignment is not None:
1406
1390
  self.highlighter.set_alignment(alignment)
@@ -1409,9 +1393,6 @@ class TranscriberTextRegion(TextAttributeRegion):
1409
1393
 
1410
1394
 
1411
1395
  class NormalizedTextRegion(TextAttributeRegion):
1412
- viewRequested = QtCore.Signal(object, object)
1413
- audioSelected = QtCore.Signal(object, object)
1414
-
1415
1396
  def __init__(
1416
1397
  self,
1417
1398
  parent,
@@ -1421,10 +1402,10 @@ class NormalizedTextRegion(TextAttributeRegion):
1421
1402
  top_point,
1422
1403
  height,
1423
1404
  selection_model: CorpusSelectionModel,
1424
- border=None,
1425
1405
  dictionary_model=None,
1426
1406
  search_term=None,
1427
1407
  speaker_id=None,
1408
+ plot_theme=None,
1428
1409
  ):
1429
1410
  super().__init__(
1430
1411
  parent,
@@ -1434,17 +1415,21 @@ class NormalizedTextRegion(TextAttributeRegion):
1434
1415
  top_point,
1435
1416
  height,
1436
1417
  selection_model,
1437
- border,
1438
1418
  dictionary_model,
1439
1419
  speaker_id,
1420
+ plot_theme,
1440
1421
  )
1441
1422
 
1442
- self.highlighter = Highlighter(self.text_edit.document())
1423
+ self.highlighter = Highlighter(self.text_item.text_edit.document())
1443
1424
  self.highlighter.set_models(dictionary_model)
1444
1425
  self.highlighter.set_speaker(speaker_id)
1445
1426
  if search_term:
1446
1427
  self.highlighter.setSearchTerm(search_term)
1447
1428
 
1429
+ def boundingRect(self):
1430
+ br = super().boundingRect()
1431
+ return br
1432
+
1448
1433
 
1449
1434
  class Highlighter(QtGui.QSyntaxHighlighter):
1450
1435
  WORDS = rf"[^\s{''.join(DEFAULT_WORD_BREAK_MARKERS)+''.join(DEFAULT_PUNCTUATION)}]+"
@@ -1537,6 +1522,7 @@ class MfaRegion(pg.LinearRegionItem):
1537
1522
  self.item = item
1538
1523
 
1539
1524
  self.settings = AnchorSettings()
1525
+ self.plot_theme = self.settings.plot_theme
1540
1526
 
1541
1527
  self.item_min = self.item.begin
1542
1528
  self.item_max = self.item.end
@@ -1552,13 +1538,13 @@ class MfaRegion(pg.LinearRegionItem):
1552
1538
  self.text_margin_pixels = 2
1553
1539
  self.height = abs(self.top_point - self.bottom_point)
1554
1540
 
1555
- self.interval_background_color = self.settings.plot_theme.interval_background_color
1556
- self.hover_line_color = self.settings.plot_theme.hover_line_color
1557
- 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
1558
1544
 
1559
- self.break_line_color = self.settings.plot_theme.break_line_color
1560
- self.text_color = self.settings.plot_theme.text_color
1561
- 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
1562
1548
  self.plot_text_font = self.settings.big_font
1563
1549
  self.setCursor(QtCore.Qt.CursorShape.SizeAllCursor)
1564
1550
  self.pen = pg.mkPen(self.break_line_color, width=3)
@@ -1681,122 +1667,131 @@ class MfaRegion(pg.LinearRegionItem):
1681
1667
  return br
1682
1668
 
1683
1669
 
1684
- class AlignmentRegion(MfaRegion):
1670
+ class IntervalTier(pg.GraphicsObject):
1671
+ highlightRequested = QtCore.Signal(object)
1672
+
1685
1673
  def __init__(
1686
1674
  self,
1687
- phone_interval: CtmInterval,
1688
- corpus_model: CorpusModel,
1689
- file_model: FileUtterancesModel,
1690
- selection_model: CorpusSelectionModel,
1691
- bottom_point: float = 0,
1692
- 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,
1693
1682
  ):
1694
- super().__init__(
1695
- phone_interval,
1696
- corpus_model,
1697
- file_model,
1698
- None,
1699
- selection_model,
1700
- bottom_point,
1701
- top_point,
1702
- )
1703
- 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
1704
1697
 
1705
- self.text = pg.TextItem(
1706
- self.item.label, anchor=(0.5, 0.5), color=self.text_color # , border=pg.mkColor("r")
1707
- )
1708
- self.text.setVisible(False)
1709
-
1710
- self.text.setFont(self.settings.font)
1711
- options = QtGui.QTextOption()
1712
- options.setWrapMode(QtGui.QTextOption.WrapMode.NoWrap)
1713
- self.text.textItem.document().setDefaultTextOption(options)
1714
- self.text.setParentItem(self)
1715
- self.per_tier_range = self.top_point - self.bottom_point
1716
- self.currentBrush = pg.mkBrush((0, 0, 0, 0))
1717
- self.selection_model.viewChanged.connect(self.check_visibility)
1718
- 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()
1719
1704
 
1720
- def setSelected(self, selected: bool):
1721
- if selected:
1722
- self.text.setColor(self.settings.plot_theme.selected_text_color)
1723
- else:
1724
- self.text.setColor(self.settings.plot_theme.text_color)
1705
+ symbol.addText(0, 0, self.plot_text_font, interval.label)
1706
+ br = symbol.boundingRect()
1725
1707
 
1726
- def check_visibility(self):
1727
- if (self.item_max - self.item_min) / (
1728
- self.selection_model.max_time - self.selection_model.min_time
1729
- ) < 0.001:
1730
- self.hide()
1731
- else:
1732
- vb = self.getViewBox()
1733
- visible_begin = max(self.item_min, self.selection_model.plot_min)
1734
- visible_end = min(self.item_max, self.selection_model.plot_max)
1735
- visible_duration = visible_end - visible_begin
1736
- self.text.setPos(
1737
- visible_begin + (visible_duration / 2), self.top_point - (self.per_tier_range / 2)
1738
- )
1739
- if vb is None:
1740
- self.text.setVisible(
1741
- (visible_end - visible_begin)
1742
- / (self.selection_model.max_time - self.selection_model.min_time)
1743
- > 0.005
1744
- )
1745
- else:
1746
- pixel_size = vb.viewPixelSize()[0]
1747
- if pixel_size != 0:
1748
- x_margin_px = 8
1749
- available_text_width = visible_duration / pixel_size - (2 * x_margin_px)
1708
+ # getting transform object
1709
+ tr = QtGui.QTransform()
1750
1710
 
1751
- self.text.setVisible(available_text_width > 10)
1752
- 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)
1753
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
1754
1721
 
1755
- class PhoneRegion(AlignmentRegion):
1756
- def __init__(
1757
- self,
1758
- phone_interval: CtmInterval,
1759
- corpus_model: CorpusModel,
1760
- file_model: FileUtterancesModel,
1761
- selection_model: CorpusSelectionModel,
1762
- bottom_point: float = 0,
1763
- top_point: float = 1,
1764
- color=None,
1765
- ):
1766
- super().__init__(
1767
- phone_interval, corpus_model, file_model, selection_model, bottom_point, top_point
1768
- )
1769
- if color is not None:
1770
- self.currentBrush = pg.mkBrush(color)
1771
- 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
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)
1772
1732
 
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
1773
1748
 
1774
- class WordRegion(AlignmentRegion):
1775
- highlightRequested = QtCore.Signal(object)
1749
+ return super().mousePressEvent(e)
1776
1750
 
1777
- def __init__(
1778
- self,
1779
- word_interval: CtmInterval,
1780
- corpus_model: CorpusModel,
1781
- file_model: FileUtterancesModel,
1782
- selection_model: CorpusSelectionModel,
1783
- bottom_point: float = 0,
1784
- top_point: float = 1,
1785
- ):
1786
- super().__init__(
1787
- word_interval, corpus_model, file_model, selection_model, bottom_point, top_point
1788
- )
1789
- self._generate_picture()
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()
1790
1777
 
1791
- def mouseClickEvent(self, ev: QtGui.QMouseEvent):
1792
- search_term = TextFilterQuery(self.item.label, word=True)
1793
- self.highlightRequested.emit(search_term)
1794
- super().mouseClickEvent(ev)
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
+ )
1795
1785
 
1796
1786
 
1797
1787
  class UtteranceRegion(MfaRegion):
1788
+ lookUpWord = QtCore.Signal(object)
1789
+ createWord = QtCore.Signal(object)
1790
+
1791
+ @profile
1798
1792
  def __init__(
1799
1793
  self,
1794
+ parent,
1800
1795
  utterance: workers.UtteranceData,
1801
1796
  corpus_model: CorpusModel,
1802
1797
  file_model: FileUtterancesModel,
@@ -1817,6 +1812,8 @@ class UtteranceRegion(MfaRegion):
1817
1812
  bottom_point,
1818
1813
  top_point,
1819
1814
  )
1815
+ self.setParentItem(parent)
1816
+ plot_theme = self.settings.plot_theme
1820
1817
  self.hide()
1821
1818
  self.item = utterance
1822
1819
  self.selection_model = selection_model
@@ -1869,44 +1866,34 @@ class UtteranceRegion(MfaRegion):
1869
1866
 
1870
1867
  self.corpus_model.utteranceTextUpdated.connect(self.update_text_from_model)
1871
1868
  self.original_text = self.item.text
1872
- self.text = UtterancePGTextItem(
1869
+ self.text_item = NormalizedTextRegion(
1870
+ self,
1873
1871
  self.item.begin,
1874
1872
  self.item.end,
1875
- self.item.text,
1873
+ self.item.normalized_text,
1874
+ self.top_point,
1875
+ self.per_tier_range,
1876
1876
  self.selection_model,
1877
- anchor=(0, 0),
1878
- top_point=self.top_point,
1879
- bottom_point=self.bottom_point,
1880
- per_tier_range=self.per_tier_range,
1881
- dictionary_model=self.dictionary_model,
1882
- speaker_id=self.item.speaker_id,
1883
- 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,
1884
1880
  )
1885
- self.text.setParentItem(self)
1886
1881
 
1887
- self.text_edit = self.text.text_edit
1888
- if not self.corpus_model.editable:
1889
- 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)
1890
1886
  self.corpus_model.editableChanged.connect(self.change_editing)
1891
- self.text_edit.setViewportMargins(
1892
- self.text_margin_pixels,
1893
- self.text_margin_pixels,
1894
- self.text_margin_pixels,
1895
- self.text_margin_pixels,
1896
- )
1897
- self.text_edit.setStyleSheet(self.settings.interval_style_sheet)
1898
1887
  self.text_edit.installEventFilter(self)
1899
- self.highlighter = Highlighter(self.text_edit.document())
1900
- self.highlighter.set_models(dictionary_model)
1901
- self.highlighter.set_speaker(self.item.speaker_id)
1902
- if search_term:
1903
- self.highlighter.setSearchTerm(search_term)
1904
1888
  self.timer = QtCore.QTimer()
1905
1889
  self.text_edit.textChanged.connect(self.refresh_timer)
1906
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)
1907
1893
  self.timer.timeout.connect(self.save_changes)
1908
1894
  self._cached_pixel_size = None
1909
1895
  self.normalized_text = None
1896
+ self.transcription_text = None
1910
1897
  i = -1
1911
1898
  for tier_name, lookup in self.extra_tiers.items():
1912
1899
  if not visible_tiers[tier_name]:
@@ -1924,23 +1911,19 @@ class UtteranceRegion(MfaRegion):
1924
1911
  tier_top_point,
1925
1912
  self.per_tier_range,
1926
1913
  self.selection_model,
1927
- border=pg.mkPen(self.settings.plot_theme.break_line_color),
1928
1914
  dictionary_model=dictionary_model,
1929
1915
  search_term=search_term,
1930
1916
  speaker_id=utterance.speaker_id,
1931
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)
1932
1920
  continue
1933
- alignment = None
1934
- intervals = getattr(self.item, lookup)
1935
- if lookup in {"transcription_text", "normalized_text"}:
1936
- if (
1937
- lookup == "transcription_text"
1938
- and self.item.text
1939
- and self.item.transcription_text
1940
- ):
1921
+ elif lookup == "transcription_text":
1922
+ alignment = None
1923
+ if self.item.normalized_text and self.item.transcription_text:
1941
1924
  alignment = pairwise2.align.globalms(
1942
- self.item.text.split(),
1943
- self.item.transcription_text.split(),
1925
+ self.item.normalized_text.lower().split(),
1926
+ self.item.transcription_text.lower().split(),
1944
1927
  0,
1945
1928
  -2,
1946
1929
  -1,
@@ -1948,18 +1931,39 @@ class UtteranceRegion(MfaRegion):
1948
1931
  gap_char=["-"],
1949
1932
  one_alignment_only=True,
1950
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)
1951
1953
 
1952
1954
  self.extra_tier_intervals[tier_name] = []
1953
1955
 
1954
1956
  if intervals is None:
1955
1957
  continue
1958
+ if not isinstance(intervals, list):
1959
+ intervals = [intervals]
1956
1960
  min_confidence = None
1957
1961
  max_confidence = None
1958
1962
  cmap = pg.ColorMap(
1959
1963
  None,
1960
1964
  [
1961
- self.settings.plot_theme.error_color,
1962
- self.settings.plot_theme.interval_background_color,
1965
+ plot_theme.error_color,
1966
+ plot_theme.interval_background_color,
1963
1967
  ],
1964
1968
  )
1965
1969
  cmap.linearize()
@@ -1971,64 +1975,49 @@ class UtteranceRegion(MfaRegion):
1971
1975
  min_confidence = interval.confidence
1972
1976
  if max_confidence is None or interval.confidence > max_confidence:
1973
1977
  max_confidence = interval.confidence
1974
- for interval in intervals:
1975
- if lookup == "transcription_text":
1976
- interval_reg = TranscriberTextRegion(
1977
- self,
1978
- self.item.begin,
1979
- self.item.end,
1980
- self.item.transcription_text,
1981
- tier_top_point,
1982
- self.per_tier_range,
1983
- self.selection_model,
1984
- border=pg.mkPen(self.settings.plot_theme.break_line_color),
1985
- alignment=alignment,
1986
- dictionary_model=dictionary_model,
1987
- search_term=search_term,
1988
- speaker_id=utterance.speaker_id,
1989
- )
1990
- interval_reg.setParentItem(self)
1991
- interval_reg.audioSelected.connect(self.setSelected)
1992
- elif "phone_intervals" in lookup:
1993
- color = None
1994
- if interval.confidence is not None and max_confidence != min_confidence:
1995
- normalized_confidence = (interval.confidence - min_confidence) / (
1996
- max_confidence - min_confidence
1997
- )
1998
- color = cmap.map(normalized_confidence)
1999
- interval_reg = PhoneRegion(
2000
- interval,
2001
- self.corpus_model,
2002
- self.file_model,
2003
- selection_model=selection_model,
2004
- top_point=tier_top_point,
2005
- bottom_point=tier_bottom_point,
2006
- color=color,
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
2007
2002
  )
2008
- interval_reg.setParentItem(self)
2009
- interval_reg.audioSelected.connect(self.setSelected)
2010
- elif "word_intervals" in lookup:
2011
- interval_reg = WordRegion(
2012
- interval,
2013
- self.corpus_model,
2014
- self.file_model,
2015
- selection_model=selection_model,
2016
- top_point=tier_top_point,
2017
- bottom_point=tier_bottom_point,
2003
+ if self.normalized_text is not None:
2004
+ interval_tier.highlightRequested.connect(
2005
+ self.normalized_text.highlighter.setSearchTerm
2018
2006
  )
2019
- interval_reg.setParentItem(self)
2020
- interval_reg.highlightRequested.connect(self.highlighter.setSearchTerm)
2021
- interval_reg.audioSelected.connect(self.setSelected)
2022
2007
 
2008
+ for interval in intervals:
2009
+ if "phone_intervals" in lookup or "word_intervals" in lookup:
2010
+ continue
2023
2011
  else:
2024
2012
  interval_reg = IntervalTextRegion(
2025
2013
  interval,
2026
2014
  self.text_color,
2027
- border=pg.mkPen(self.settings.plot_theme.break_line_color, width=3),
2015
+ border=pg.mkPen(plot_theme.break_line_color, width=1),
2028
2016
  top_point=tier_top_point,
2029
2017
  height=self.per_tier_range,
2030
2018
  background_brush=self.background_brush,
2031
2019
  selected_brush=pg.mkBrush(self.selected_interval_color),
2020
+ plot_theme=self.plot_theme,
2032
2021
  )
2033
2022
  interval_reg.audioSelected.connect(self.setSelected)
2034
2023
  interval_reg.setParentItem(self)
@@ -2037,9 +2026,23 @@ class UtteranceRegion(MfaRegion):
2037
2026
  interval_reg.viewRequested.connect(self.viewRequested.emit)
2038
2027
  self.extra_tier_intervals[tier_name].append(interval_reg)
2039
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)
2040
2031
  self.show()
2041
2032
  self.available_speakers = available_speakers
2042
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
+
2043
2046
  def show(self):
2044
2047
  for intervals in self.extra_tier_intervals.values():
2045
2048
  for interval in intervals:
@@ -2056,14 +2059,6 @@ class UtteranceRegion(MfaRegion):
2056
2059
  interval.hide()
2057
2060
  super().show()
2058
2061
 
2059
- def setSelected(self, selected: bool):
2060
- if selected:
2061
- if not self.selected:
2062
- self.text_edit.setFocus()
2063
- else:
2064
- self.text_edit.clearFocus()
2065
- super().setSelected(selected)
2066
-
2067
2062
  def change_editing(self, editable: bool):
2068
2063
  self.lines[0].movable = editable
2069
2064
  self.lines[1].movable = editable
@@ -2083,7 +2078,7 @@ class UtteranceRegion(MfaRegion):
2083
2078
  self.movable = False
2084
2079
  self.setAcceptHoverEvents(False)
2085
2080
 
2086
- def contextMenuEvent(self, ev: QtWidgets.QGraphicsSceneContextMenuEvent):
2081
+ def construct_context_menu(self):
2087
2082
  menu = QtWidgets.QMenu()
2088
2083
  change_speaker_menu = QtWidgets.QMenu("Change speaker")
2089
2084
  a = QtGui.QAction(menu)
@@ -2128,6 +2123,26 @@ class UtteranceRegion(MfaRegion):
2128
2123
  change_speaker_menu.setStyleSheet(self.settings.menu_style_sheet)
2129
2124
  visible_tiers_menu.setStyleSheet(self.settings.menu_style_sheet)
2130
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()
2131
2146
  menu.exec_(ev.screenPos())
2132
2147
 
2133
2148
  def update_tier_visibility(self, checked):
@@ -2280,7 +2295,7 @@ class UtteranceRegion(MfaRegion):
2280
2295
  if utterance_id != self.item.id or new_text == self.original_text:
2281
2296
  return
2282
2297
  self.original_text = new_text
2283
- with QtCore.QSignalBlocker(self.text.text_edit):
2298
+ with QtCore.QSignalBlocker(self.text_item.text_edit):
2284
2299
  position = self.text_edit.textCursor().position()
2285
2300
  end_offset = self.text_edit.document().characterCount() - position
2286
2301
  self.text_edit.setPlainText(new_text)
@@ -2330,10 +2345,11 @@ class WaveForm(pg.PlotCurveItem):
2330
2345
  class PitchTrack(pg.PlotCurveItem):
2331
2346
  def __init__(self, bottom_point, top_point):
2332
2347
  self.settings = AnchorSettings()
2348
+ self.plot_theme = self.settings.plot_theme
2333
2349
  self.top_point = top_point
2334
2350
  self.bottom_point = bottom_point
2335
2351
  self.mid_point = (self.top_point + self.bottom_point) / 2
2336
- pen = pg.mkPen(self.settings.plot_theme.pitch_color, width=3)
2352
+ pen = pg.mkPen(self.plot_theme.pitch_color, width=3)
2337
2353
  super().__init__()
2338
2354
  self.setPen(pen)
2339
2355
  self.channel = 0
@@ -2342,24 +2358,24 @@ class PitchTrack(pg.PlotCurveItem):
2342
2358
  self.setAcceptHoverEvents(False)
2343
2359
  self.min_label = pg.TextItem(
2344
2360
  str(self.settings.PITCH_MIN_F0),
2345
- self.settings.plot_theme.pitch_color,
2361
+ self.plot_theme.pitch_color,
2346
2362
  anchor=(1, 1),
2347
2363
  )
2348
2364
  self.min_label.setFont(self.settings.font)
2349
2365
  self.min_label.setParentItem(self)
2350
2366
  self.max_label = pg.TextItem(
2351
2367
  str(self.settings.PITCH_MAX_F0),
2352
- self.settings.plot_theme.pitch_color,
2368
+ self.plot_theme.pitch_color,
2353
2369
  anchor=(1, 0),
2354
2370
  )
2355
2371
  self.max_label.setFont(self.settings.font)
2356
2372
  self.max_label.setParentItem(self)
2357
2373
 
2358
2374
  def update_theme(self):
2359
- pen = pg.mkPen(self.settings.plot_theme.pitch_color, width=3)
2375
+ pen = pg.mkPen(self.plot_theme.pitch_color, width=3)
2360
2376
  self.setPen(pen)
2361
- self.min_label.setColor(self.settings.plot_theme.pitch_color)
2362
- 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)
2363
2379
 
2364
2380
  def hoverEvent(self, ev):
2365
2381
  return
@@ -2377,6 +2393,7 @@ class PitchTrack(pg.PlotCurveItem):
2377
2393
  class Spectrogram(pg.ImageItem):
2378
2394
  def __init__(self, bottom_point, top_point):
2379
2395
  self.settings = AnchorSettings()
2396
+ self.plot_theme = self.settings.plot_theme
2380
2397
  self.top_point = top_point
2381
2398
  self.bottom_point = bottom_point
2382
2399
  self.selection_model = None
@@ -2385,8 +2402,8 @@ class Spectrogram(pg.ImageItem):
2385
2402
  self.cmap = pg.ColorMap(
2386
2403
  None,
2387
2404
  [
2388
- self.settings.plot_theme.background_color,
2389
- self.settings.plot_theme.spectrogram_color,
2405
+ self.plot_theme.background_color,
2406
+ self.plot_theme.spectrogram_color,
2390
2407
  ],
2391
2408
  )
2392
2409
  self.cmap.linearize()
@@ -2402,8 +2419,8 @@ class Spectrogram(pg.ImageItem):
2402
2419
  self.cmap = pg.ColorMap(
2403
2420
  None,
2404
2421
  [
2405
- self.settings.plot_theme.background_color,
2406
- self.settings.plot_theme.spectrogram_color,
2422
+ self.plot_theme.background_color,
2423
+ self.plot_theme.spectrogram_color,
2407
2424
  ],
2408
2425
  )
2409
2426
  self.cmap.linearize()
@@ -2485,6 +2502,7 @@ class AudioPlots(pg.GraphicsObject):
2485
2502
  def __init__(self, top_point, separator_point, bottom_point):
2486
2503
  super().__init__()
2487
2504
  self.settings = AnchorSettings()
2505
+ self.plot_theme = self.settings.plot_theme
2488
2506
  self.selection_model: typing.Optional[FileSelectionModel] = None
2489
2507
  self.top_point = top_point
2490
2508
  self.separator_point = separator_point
@@ -2495,20 +2513,24 @@ class AudioPlots(pg.GraphicsObject):
2495
2513
  self.wave_form.setParentItem(self)
2496
2514
  self.spectrogram.setParentItem(self)
2497
2515
  self.pitch_track.setParentItem(self)
2498
- color = self.settings.plot_theme.selected_range_color
2516
+ color = self.plot_theme.selected_range_color
2499
2517
  color.setAlphaF(0.25)
2500
2518
  self.selection_brush = pg.mkBrush(color)
2501
- self.background_pen = pg.mkPen(self.settings.plot_theme.break_line_color)
2502
- 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)
2503
2521
  self.selection_area = SelectionArea(
2504
2522
  top_point=self.top_point,
2505
2523
  bottom_point=self.bottom_point,
2506
2524
  brush=self.selection_brush,
2507
2525
  clipItem=self,
2508
- pen=pg.mkPen(self.settings.plot_theme.selected_interval_color),
2526
+ pen=pg.mkPen(self.plot_theme.selected_interval_color),
2509
2527
  )
2510
2528
  self.selection_area.setParentItem(self)
2511
2529
 
2530
+ self.play_timer = QtCore.QTimer()
2531
+ self.play_timer.setInterval(1)
2532
+ self.play_timer.timeout.connect(self.update_play_line)
2533
+
2512
2534
  self.play_line = pg.InfiniteLine(
2513
2535
  pos=-20,
2514
2536
  span=(0, 1),
@@ -2521,7 +2543,7 @@ class AudioPlots(pg.GraphicsObject):
2521
2543
  pos=-20,
2522
2544
  span=(0, 1),
2523
2545
  pen=pg.mkPen(
2524
- self.settings.plot_theme.selected_interval_color,
2546
+ self.plot_theme.selected_interval_color,
2525
2547
  width=3,
2526
2548
  style=QtCore.Qt.PenStyle.DashLine,
2527
2549
  ),
@@ -2647,12 +2669,17 @@ class AudioPlots(pg.GraphicsObject):
2647
2669
  br = QtCore.QRectF(self.picture.boundingRect())
2648
2670
  return br
2649
2671
 
2650
- def update_play_line(self, time):
2672
+ def update_play_line(self, time=None):
2651
2673
  if time is None:
2652
2674
  return
2675
+ self.play_line.setVisible(
2676
+ self.selection_model.min_time <= time <= self.selection_model.max_time
2677
+ )
2653
2678
  self.play_line.setPos(time)
2654
2679
 
2655
2680
  def update_plot(self):
2681
+ self.setVisible(False)
2682
+ self.play_line.setVisible(False)
2656
2683
  if (
2657
2684
  self.selection_model.model().file is None
2658
2685
  or self.selection_model.model().file.sound_file is None
@@ -2662,9 +2689,8 @@ class AudioPlots(pg.GraphicsObject):
2662
2689
  self.rect.setLeft(self.selection_model.plot_min)
2663
2690
  self.rect.setRight(self.selection_model.plot_max)
2664
2691
  self._generate_picture()
2665
- self.update_play_line(self.selection_model.plot_min)
2666
- self.selection_area.update_region()
2667
- self.update()
2692
+ # self.selection_area.update_region()
2693
+ self.setVisible(True)
2668
2694
 
2669
2695
 
2670
2696
  class SpeakerTier(pg.GraphicsObject):
@@ -2690,14 +2716,13 @@ class SpeakerTier(pg.GraphicsObject):
2690
2716
  self.selection_model = selection_model
2691
2717
  self.dictionary_model = dictionary_model
2692
2718
  self.settings = AnchorSettings()
2719
+ self.plot_theme = self.settings.plot_theme
2693
2720
  self.search_term = search_term
2694
2721
  self.speaker_id = speaker_id
2695
2722
  self.speaker_name = speaker_name
2696
2723
  self.speaker_index = 0
2697
2724
  self.top_point = top_point
2698
- self.speaker_label = pg.TextItem(
2699
- self.speaker_name, color=self.settings.plot_theme.break_line_color
2700
- )
2725
+ self.speaker_label = pg.TextItem(self.speaker_name, color=self.plot_theme.break_line_color)
2701
2726
  self.speaker_label.setFont(self.settings.font)
2702
2727
  self.speaker_label.setParentItem(self)
2703
2728
  self.speaker_label.setZValue(40)
@@ -2705,8 +2730,8 @@ class SpeakerTier(pg.GraphicsObject):
2705
2730
  self.annotation_range = self.top_point - self.bottom_point
2706
2731
  self.extra_tiers = {}
2707
2732
  self.visible_utterances: dict[str, UtteranceRegion] = {}
2708
- self.background_brush = pg.mkBrush(self.settings.plot_theme.background_color)
2709
- 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)
2710
2735
  self.picture = QtGui.QPicture()
2711
2736
  self.has_visible_utterances = False
2712
2737
  self.has_selected_utterances = False
@@ -2722,6 +2747,7 @@ class SpeakerTier(pg.GraphicsObject):
2722
2747
  self.selection_model.selectionChanged.connect(self.update_select)
2723
2748
  self.selection_model.model().utterancesReady.connect(self.refresh)
2724
2749
  self.available_speakers = {}
2750
+ self.painter_path_cache = {True: {}, False: {}}
2725
2751
 
2726
2752
  def wheelEvent(self, ev):
2727
2753
  self.receivedWheelEvent.emit(ev)
@@ -2736,6 +2762,10 @@ class SpeakerTier(pg.GraphicsObject):
2736
2762
  p.drawPicture(0, 0, self.picture)
2737
2763
 
2738
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)
2739
2769
  self.picture = QtGui.QPicture()
2740
2770
  painter = QtGui.QPainter(self.picture)
2741
2771
  painter.setPen(self.border)
@@ -2760,7 +2790,7 @@ class SpeakerTier(pg.GraphicsObject):
2760
2790
 
2761
2791
  def setSearchTerm(self, term):
2762
2792
  for utt in self.visible_utterances.values():
2763
- utt.highlighter.setSearchTerm(term)
2793
+ utt.text_item.highlighter.setSearchTerm(term)
2764
2794
 
2765
2795
  def refreshTexts(self, utt_id, text):
2766
2796
  for reg in self.visible_utterances.values():
@@ -2776,6 +2806,7 @@ class SpeakerTier(pg.GraphicsObject):
2776
2806
  reg.scene().removeItem(reg)
2777
2807
  self.visible_utterances = {}
2778
2808
 
2809
+ @profile
2779
2810
  def refresh(self, *args):
2780
2811
  self.hide()
2781
2812
  if self.selection_model.plot_min is None:
@@ -2820,6 +2851,7 @@ class SpeakerTier(pg.GraphicsObject):
2820
2851
  self.has_visible_utterances = True
2821
2852
  # Utterance region always at the top
2822
2853
  reg = UtteranceRegion(
2854
+ self,
2823
2855
  u,
2824
2856
  self.corpus_model,
2825
2857
  self.file_model,
@@ -2845,7 +2877,6 @@ class SpeakerTier(pg.GraphicsObject):
2845
2877
  reg.viewRequested.connect(self.selection_model.set_view_times)
2846
2878
  reg.textEdited.connect(self.update_utterance_text)
2847
2879
  reg.selectRequested.connect(self.selection_model.update_select)
2848
- reg.setParentItem(self)
2849
2880
  self.visible_utterances[u.id] = reg
2850
2881
 
2851
2882
  self.show()
@@ -2895,13 +2926,7 @@ class SpeakerTier(pg.GraphicsObject):
2895
2926
  if other_begin < end <= other_end or end > other_begin > other_end > beg:
2896
2927
  reg.setRegion([beg, other_begin])
2897
2928
  break
2898
- reg.text.begin, reg.text.end = reg.getRegion()
2899
- reg.text.update_times(self.selection_model.plot_min, self.selection_model.plot_max)
2900
- if reg.normalized_text is not None:
2901
- reg.normalized_text.text.begin, reg.normalized_text.text.end = reg.getRegion()
2902
- reg.normalized_text.text.update_times(
2903
- self.selection_model.plot_min, self.selection_model.plot_max
2904
- )
2929
+ reg.update_edit_fields()
2905
2930
  reg.select_self()
2906
2931
  reg.update()
2907
2932
 
@@ -2916,7 +2941,5 @@ class SpeakerTier(pg.GraphicsObject):
2916
2941
  return
2917
2942
  self.selection_model.model().update_utterance_times(utt, begin=new_begin, end=new_end)
2918
2943
  self.selection_model.request_start_time(new_begin)
2919
- reg.text.begin = new_begin
2920
- reg.text.end = new_end
2921
- reg.update()
2944
+ reg.update_edit_fields()
2922
2945
  self.lineDragFinished.emit(True)