Anchor-annotator 0.8.2__py3-none-any.whl → 0.9.1__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/_version.py +2 -2
- anchor/command_line.py +1 -0
- anchor/main.py +113 -6
- anchor/models.py +402 -23
- anchor/plot.py +835 -104
- anchor/settings.py +6 -10
- anchor/ui_main_window.py +14 -8
- anchor/undo.py +682 -11
- anchor/widgets.py +303 -39
- anchor/workers.py +632 -351
- {anchor_annotator-0.8.2.dist-info → anchor_annotator-0.9.1.dist-info}/METADATA +1 -1
- anchor_annotator-0.9.1.dist-info/RECORD +22 -0
- anchor_annotator-0.8.2.dist-info/RECORD +0 -22
- {anchor_annotator-0.8.2.dist-info → anchor_annotator-0.9.1.dist-info}/WHEEL +0 -0
- {anchor_annotator-0.8.2.dist-info → anchor_annotator-0.9.1.dist-info}/licenses/LICENSE +0 -0
- {anchor_annotator-0.8.2.dist-info → anchor_annotator-0.9.1.dist-info}/top_level.txt +0 -0
anchor/plot.py
CHANGED
@@ -11,8 +11,16 @@ import numpy as np
|
|
11
11
|
import pyqtgraph as pg
|
12
12
|
import sqlalchemy
|
13
13
|
from Bio import pairwise2
|
14
|
-
from montreal_forced_aligner.
|
15
|
-
|
14
|
+
from montreal_forced_aligner.db import (
|
15
|
+
PhoneInterval,
|
16
|
+
Pronunciation,
|
17
|
+
ReferencePhoneInterval,
|
18
|
+
ReferenceWordInterval,
|
19
|
+
Speaker,
|
20
|
+
Utterance,
|
21
|
+
Word,
|
22
|
+
WordInterval,
|
23
|
+
)
|
16
24
|
from montreal_forced_aligner.dictionary.mixins import (
|
17
25
|
DEFAULT_PUNCTUATION,
|
18
26
|
DEFAULT_WORD_BREAK_MARKERS,
|
@@ -188,15 +196,18 @@ class UtteranceClusterView(pg.PlotWidget):
|
|
188
196
|
pen=pg.mkPen(self.settings.value(self.settings.MAIN_TEXT_COLOR)),
|
189
197
|
labelTextColor=self.settings.value(self.settings.MAIN_TEXT_COLOR),
|
190
198
|
)
|
199
|
+
self.pen_colormap = pg.ColorMap(None, [0.0, 1.0])
|
200
|
+
self.pens = []
|
191
201
|
self.legend_item.changeCluster.connect(self.change_cluster)
|
192
202
|
self.legend_item.setParentItem(self.getPlotItem())
|
193
203
|
self.legend_item.setFont(self.settings.font)
|
194
204
|
self.selected_indices = set()
|
195
205
|
# self.addItem(self.legend_item)
|
196
|
-
self.selected_pen = pg.mkPen(
|
206
|
+
self.selected_pen = pg.mkPen(
|
207
|
+
self.settings.value(self.settings.PRIMARY_LIGHT_COLOR), width=2
|
208
|
+
)
|
197
209
|
self.updated_pen = pg.mkPen(self.settings.value(self.settings.MAIN_TEXT_COLOR), width=2)
|
198
210
|
self.hover_pen = pg.mkPen(self.settings.value(self.settings.ACCENT_LIGHT_COLOR), width=2)
|
199
|
-
self.base_pen = pg.mkPen(self.settings.value(self.settings.PRIMARY_DARK_COLOR), width=1)
|
200
211
|
self.selection_timer = QtCore.QTimer()
|
201
212
|
self.selection_timer.setInterval(300)
|
202
213
|
self.selection_timer.timeout.connect(self.send_selection_update)
|
@@ -253,6 +264,8 @@ class UtteranceClusterView(pg.PlotWidget):
|
|
253
264
|
if ev.button() == QtCore.Qt.MouseButton.LeftButton:
|
254
265
|
utterance_id = int(self.speaker_model.utterance_ids[index])
|
255
266
|
utterance = self.corpus_model.session.query(Utterance).get(utterance_id)
|
267
|
+
if utterance is None:
|
268
|
+
return
|
256
269
|
self.selection_model.set_current_file(
|
257
270
|
utterance.file_id,
|
258
271
|
utterance.begin,
|
@@ -260,6 +273,7 @@ class UtteranceClusterView(pg.PlotWidget):
|
|
260
273
|
utterance.id,
|
261
274
|
utterance.speaker_id,
|
262
275
|
force_update=True,
|
276
|
+
single_utterance=False,
|
263
277
|
)
|
264
278
|
else:
|
265
279
|
brush_indices = list(self.brushes.keys())
|
@@ -301,6 +315,7 @@ class UtteranceClusterView(pg.PlotWidget):
|
|
301
315
|
self.brushes[x if x in self.speaker_model.current_speakers else -1]
|
302
316
|
for x in self.speaker_model.cluster_labels
|
303
317
|
]
|
318
|
+
self.pens = [pg.mkPen(x, width=2) for x in self.speaker_model.distances]
|
304
319
|
xmin, xmax = np.min(self.speaker_model.mds[:, 0]), np.max(self.speaker_model.mds[:, 0])
|
305
320
|
ymin, ymax = np.min(self.speaker_model.mds[:, 1]), np.max(self.speaker_model.mds[:, 1])
|
306
321
|
xrange = xmax - xmin
|
@@ -328,7 +343,7 @@ class UtteranceClusterView(pg.PlotWidget):
|
|
328
343
|
pos=self.speaker_model.mds,
|
329
344
|
size=10,
|
330
345
|
brush=brushes,
|
331
|
-
pen=self.
|
346
|
+
pen=self.pens,
|
332
347
|
hoverPen=self.hover_pen,
|
333
348
|
hoverable=True,
|
334
349
|
)
|
@@ -360,7 +375,7 @@ class UtteranceClusterView(pg.PlotWidget):
|
|
360
375
|
elif i in self.updated_indices:
|
361
376
|
pens.append(self.updated_pen)
|
362
377
|
else:
|
363
|
-
pens.append(self.
|
378
|
+
pens.append(self.pens[i])
|
364
379
|
self.scatter_item.setPen(pens)
|
365
380
|
|
366
381
|
|
@@ -528,6 +543,7 @@ class UtteranceView(QtWidgets.QWidget):
|
|
528
543
|
t.set_models(corpus_model, selection_model, dictionary_model)
|
529
544
|
self.audio_plot.set_models(self.selection_model)
|
530
545
|
self.selection_model.viewChanged.connect(self.update_plot)
|
546
|
+
self.selection_model.searchTermChanged.connect(self.set_search_term)
|
531
547
|
# self.corpus_model.utteranceTextUpdated.connect(self.refresh_utterance_text)
|
532
548
|
self.selection_model.resetView.connect(self.reset_plot)
|
533
549
|
self.file_model.utterancesReady.connect(self.finalize_loading_utterances)
|
@@ -558,7 +574,6 @@ class UtteranceView(QtWidgets.QWidget):
|
|
558
574
|
if self.file_model.file is None:
|
559
575
|
return
|
560
576
|
scroll_to = None
|
561
|
-
|
562
577
|
self.speaker_tiers = {}
|
563
578
|
self.speaker_tier_items = {}
|
564
579
|
self.speaker_tier_layout.clear()
|
@@ -683,25 +698,18 @@ class UtteranceView(QtWidgets.QWidget):
|
|
683
698
|
self.extra_tiers["Normalized text"] = "normalized_text"
|
684
699
|
self.extra_tiers["Transcription"] = "transcription_text"
|
685
700
|
if self.corpus_model.has_alignments and "Words" not in self.extra_tiers:
|
686
|
-
self.extra_tiers["Words"] = "
|
687
|
-
self.extra_tiers["Phones"] = "
|
688
|
-
if self.corpus_model.has_reference_alignments and "Reference" not in self.extra_tiers:
|
689
|
-
self.extra_tiers["Reference"] = "reference_phone_intervals"
|
690
|
-
if (
|
691
|
-
self.corpus_model.has_transcribed_alignments
|
692
|
-
and "Transcription" not in self.extra_tiers
|
693
|
-
):
|
694
|
-
self.extra_tiers["Transcribed words"] = "transcribed_word_intervals"
|
695
|
-
self.extra_tiers["Transcribed phones"] = "transcribed_phone_intervals"
|
701
|
+
self.extra_tiers["Words"] = "word_intervals"
|
702
|
+
self.extra_tiers["Phones"] = "phone_intervals"
|
696
703
|
if (
|
697
|
-
self.corpus_model.
|
698
|
-
and "
|
704
|
+
self.corpus_model.has_reference_alignments
|
705
|
+
and "Reference phones" not in self.extra_tiers
|
699
706
|
):
|
700
|
-
self.extra_tiers["
|
701
|
-
self.extra_tiers["
|
707
|
+
self.extra_tiers["Reference words"] = "reference_word_intervals"
|
708
|
+
self.extra_tiers["Reference phones"] = "reference_phone_intervals"
|
702
709
|
|
703
|
-
def set_search_term(self):
|
704
|
-
term
|
710
|
+
def set_search_term(self, term: TextFilterQuery = None):
|
711
|
+
if term is None:
|
712
|
+
term = self.corpus_model.text_filter
|
705
713
|
if not term:
|
706
714
|
return
|
707
715
|
self.search_term = term
|
@@ -766,6 +774,7 @@ class UtteranceView(QtWidgets.QWidget):
|
|
766
774
|
|
767
775
|
class UtteranceLine(pg.InfiniteLine):
|
768
776
|
hoverChanged = QtCore.Signal(object)
|
777
|
+
snapModeChanged = QtCore.Signal(object)
|
769
778
|
|
770
779
|
def __init__(
|
771
780
|
self, *args, movingPen=None, view_min=None, view_max=None, initial=True, **kwargs
|
@@ -798,6 +807,7 @@ class UtteranceLine(pg.InfiniteLine):
|
|
798
807
|
|
799
808
|
def mouseDragEvent(self, ev):
|
800
809
|
if self.movable and ev.button() == QtCore.Qt.MouseButton.LeftButton:
|
810
|
+
self.snapModeChanged.emit(ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier)
|
801
811
|
if ev.isStart() and (
|
802
812
|
(self.initial and self.pos().x() - self.mapToParent(ev.buttonDownPos()).x() < 0)
|
803
813
|
or (
|
@@ -1224,7 +1234,7 @@ class IntervalTextRegion(pg.GraphicsObject):
|
|
1224
1234
|
|
1225
1235
|
def __init__(
|
1226
1236
|
self,
|
1227
|
-
interval
|
1237
|
+
interval,
|
1228
1238
|
color,
|
1229
1239
|
top_point,
|
1230
1240
|
height,
|
@@ -1544,7 +1554,7 @@ class MfaRegion(pg.LinearRegionItem):
|
|
1544
1554
|
|
1545
1555
|
def __init__(
|
1546
1556
|
self,
|
1547
|
-
item
|
1557
|
+
item,
|
1548
1558
|
corpus_model: CorpusModel,
|
1549
1559
|
file_model: FileUtterancesModel,
|
1550
1560
|
dictionary_model: typing.Optional[DictionaryTableModel],
|
@@ -1702,41 +1712,244 @@ class MfaRegion(pg.LinearRegionItem):
|
|
1702
1712
|
return br
|
1703
1713
|
|
1704
1714
|
|
1715
|
+
class IntervalLine(pg.InfiniteLine):
|
1716
|
+
hoverChanged = QtCore.Signal(object, object)
|
1717
|
+
|
1718
|
+
def __init__(
|
1719
|
+
self,
|
1720
|
+
pos,
|
1721
|
+
index=None,
|
1722
|
+
index_count=None,
|
1723
|
+
pen=None,
|
1724
|
+
movingPen=None,
|
1725
|
+
hoverPen=None,
|
1726
|
+
bottom_point: float = 0,
|
1727
|
+
top_point: float = 1,
|
1728
|
+
bound_min=None,
|
1729
|
+
bound_max=None,
|
1730
|
+
movable=True,
|
1731
|
+
):
|
1732
|
+
super().__init__(
|
1733
|
+
pos,
|
1734
|
+
angle=90,
|
1735
|
+
span=(bottom_point, top_point),
|
1736
|
+
pen=pen,
|
1737
|
+
hoverPen=hoverPen,
|
1738
|
+
movable=movable,
|
1739
|
+
)
|
1740
|
+
self.index = index
|
1741
|
+
self.index_count = index_count
|
1742
|
+
self.initial = index <= 0
|
1743
|
+
self.final = index >= index_count - 1
|
1744
|
+
self.bound_min = bound_min
|
1745
|
+
self.bound_max = bound_max
|
1746
|
+
self.movingPen = movingPen
|
1747
|
+
self.bounding_width = 0.1
|
1748
|
+
if self.movable:
|
1749
|
+
self.setCursor(QtCore.Qt.CursorShape.SizeHorCursor)
|
1750
|
+
|
1751
|
+
def setMouseHover(self, hover):
|
1752
|
+
if hover and self.movable:
|
1753
|
+
self.setCursor(QtCore.Qt.CursorShape.SizeHorCursor)
|
1754
|
+
elif self.movable:
|
1755
|
+
self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
|
1756
|
+
self.hoverChanged.emit(hover, self)
|
1757
|
+
super().setMouseHover(hover)
|
1758
|
+
|
1759
|
+
def _computeBoundingRect(self):
|
1760
|
+
# br = UIGraphicsItem.boundingRect(self)
|
1761
|
+
vr = self.viewRect() # bounds of containing ViewBox mapped to local coords.
|
1762
|
+
if vr is None:
|
1763
|
+
return QtCore.QRectF()
|
1764
|
+
|
1765
|
+
# add a 4-pixel radius around the line for mouse interaction.
|
1766
|
+
px = self.pixelLength(
|
1767
|
+
direction=pg.Point(1, 0), ortho=True
|
1768
|
+
) # get pixel length orthogonal to the line
|
1769
|
+
if px is None:
|
1770
|
+
px = 0
|
1771
|
+
pw = max(self.pen.width() / 2, self.hoverPen.width() / 2)
|
1772
|
+
w = max(self.bounding_width, self._maxMarkerSize + pw) + 5
|
1773
|
+
w = w * px
|
1774
|
+
br = QtCore.QRectF(vr)
|
1775
|
+
br.setBottom(-w)
|
1776
|
+
br.setTop(w)
|
1777
|
+
|
1778
|
+
left = self.span[0]
|
1779
|
+
right = self.span[1]
|
1780
|
+
|
1781
|
+
br.setLeft(left)
|
1782
|
+
br.setRight(right)
|
1783
|
+
br = br.normalized()
|
1784
|
+
|
1785
|
+
vs = self.getViewBox().size()
|
1786
|
+
|
1787
|
+
if self._bounds != br or self._lastViewSize != vs:
|
1788
|
+
self._bounds = br
|
1789
|
+
self._lastViewSize = vs
|
1790
|
+
self.prepareGeometryChange()
|
1791
|
+
|
1792
|
+
self._endPoints = (left, right)
|
1793
|
+
self._lastViewRect = vr
|
1794
|
+
|
1795
|
+
return self._bounds
|
1796
|
+
|
1797
|
+
def hoverEvent(self, ev):
|
1798
|
+
if (
|
1799
|
+
(not ev.isExit())
|
1800
|
+
and self.movable
|
1801
|
+
# and (
|
1802
|
+
# (self.initial and self.pos().x() - self.mapToParent(ev.pos()).x() < 0)
|
1803
|
+
# or (not self.initial and self.pos().x() - self.mapToParent(ev.pos()).x() > 0)
|
1804
|
+
# )
|
1805
|
+
and ev.acceptDrags(QtCore.Qt.MouseButton.LeftButton)
|
1806
|
+
):
|
1807
|
+
self.setMouseHover(True)
|
1808
|
+
else:
|
1809
|
+
self.setMouseHover(False)
|
1810
|
+
|
1811
|
+
def mouseDragEvent(self, ev):
|
1812
|
+
if self.movable and ev.button() == QtCore.Qt.MouseButton.LeftButton:
|
1813
|
+
if ev.isStart():
|
1814
|
+
self.moving = True
|
1815
|
+
self._boundingRect = None
|
1816
|
+
self.currentPen = self.movingPen
|
1817
|
+
self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos())
|
1818
|
+
self.startPosition = self.pos()
|
1819
|
+
ev.accept()
|
1820
|
+
|
1821
|
+
if not self.moving:
|
1822
|
+
return
|
1823
|
+
p = self.cursorOffset + self.mapToParent(ev.pos())
|
1824
|
+
p.setY(self.startPosition.y())
|
1825
|
+
if p.x() >= self.bound_max - 0.01:
|
1826
|
+
p.setX(self.bound_max - 0.01)
|
1827
|
+
if p.x() <= self.bound_min + 0.01:
|
1828
|
+
p.setX(self.bound_min + 0.01)
|
1829
|
+
self.setPos(p)
|
1830
|
+
self.sigDragged.emit(self)
|
1831
|
+
if ev.isFinish():
|
1832
|
+
self.currentPen = self.pen
|
1833
|
+
self.moving = False
|
1834
|
+
self.sigPositionChangeFinished.emit(self)
|
1835
|
+
|
1836
|
+
|
1705
1837
|
class IntervalTier(pg.GraphicsObject):
|
1706
1838
|
highlightRequested = QtCore.Signal(object)
|
1707
1839
|
|
1708
1840
|
def __init__(
|
1709
1841
|
self,
|
1710
|
-
parent,
|
1711
|
-
utterance:
|
1712
|
-
intervals: typing.List[
|
1842
|
+
parent: UtteranceRegion,
|
1843
|
+
utterance: Utterance,
|
1844
|
+
intervals: typing.List[typing.Union[PhoneInterval, WordInterval]],
|
1713
1845
|
selection_model: FileSelectionModel,
|
1714
|
-
top_point,
|
1715
|
-
bottom_point,
|
1716
|
-
|
1846
|
+
top_point: float,
|
1847
|
+
bottom_point: float,
|
1848
|
+
lookup: typing.Optional[str] = None,
|
1849
|
+
movable: bool = False,
|
1717
1850
|
):
|
1718
1851
|
super().__init__()
|
1719
1852
|
self.setParentItem(parent)
|
1720
1853
|
self.intervals = intervals
|
1721
|
-
self.
|
1854
|
+
self.lookup = lookup
|
1855
|
+
self.settings = AnchorSettings()
|
1856
|
+
self.plot_theme = self.settings.plot_theme
|
1857
|
+
self.anchor = pg.Point((0.5, 0.5))
|
1858
|
+
self.plot_text_font = self.settings.font
|
1859
|
+
self.movable = movable
|
1860
|
+
|
1861
|
+
self.background_color = self.plot_theme.background_color
|
1862
|
+
self.hover_line_color = self.plot_theme.hover_line_color
|
1863
|
+
self.moving_line_color = self.plot_theme.moving_line_color
|
1864
|
+
self.break_line_color = self.plot_theme.break_line_color
|
1865
|
+
self.text_color = self.plot_theme.text_color
|
1866
|
+
self.selected_interval_color = self.plot_theme.selected_interval_color
|
1867
|
+
self.highlight_interval_color = self.plot_theme.break_line_color
|
1868
|
+
self.highlight_text_color = self.plot_theme.background_color
|
1869
|
+
|
1870
|
+
self.top_point = top_point
|
1871
|
+
self.bottom_point = bottom_point
|
1872
|
+
self.selection_model = selection_model
|
1873
|
+
self.utterance = utterance
|
1874
|
+
self.lines = []
|
1875
|
+
self.selected = []
|
1876
|
+
|
1877
|
+
self._boundingRectCache = None
|
1878
|
+
self._cached_pixel_size = None
|
1879
|
+
|
1880
|
+
self.hoverPen = pg.mkPen(self.hover_line_color, width=3)
|
1881
|
+
self.movingPen = pg.mkPen(
|
1882
|
+
self.moving_line_color, width=3, style=QtCore.Qt.PenStyle.DashLine
|
1883
|
+
)
|
1884
|
+
self.border_pen = pg.mkPen(self.break_line_color, width=3)
|
1885
|
+
self.border_pen.setCapStyle(QtCore.Qt.PenCapStyle.FlatCap)
|
1886
|
+
self.text_pen = pg.mkPen(self.text_color)
|
1887
|
+
self.text_brush = pg.mkBrush(self.text_color)
|
1888
|
+
self.highlight_text_pen = pg.mkPen(self.highlight_text_color)
|
1889
|
+
self.highlight_text_brush = pg.mkBrush(self.highlight_text_color)
|
1890
|
+
self.search_term = None
|
1891
|
+
self.search_regex = None
|
1892
|
+
self.update_intervals(self.utterance)
|
1893
|
+
|
1894
|
+
def refresh_boundaries(self, interval_id, new_time):
|
1895
|
+
for index, interval in enumerate(self.intervals):
|
1896
|
+
if interval.id == interval_id:
|
1897
|
+
try:
|
1898
|
+
self.lines[index].setPos(new_time)
|
1899
|
+
except IndexError:
|
1900
|
+
pass
|
1901
|
+
break
|
1902
|
+
self.refresh_tier()
|
1903
|
+
|
1904
|
+
def update_intervals(self, utterance):
|
1905
|
+
self.intervals = sorted(
|
1906
|
+
getattr(utterance, self.lookup, self.intervals), key=lambda x: x.begin
|
1907
|
+
)
|
1908
|
+
for line in self.lines:
|
1909
|
+
if line.scene() is not None:
|
1910
|
+
line.scene().removeItem(line)
|
1911
|
+
self.lines = []
|
1912
|
+
bound_min = self.utterance.begin
|
1913
|
+
for i, interval in enumerate(self.intervals):
|
1914
|
+
if i == 0:
|
1915
|
+
continue
|
1916
|
+
|
1917
|
+
line = IntervalLine(
|
1918
|
+
interval.begin,
|
1919
|
+
index=i - 1,
|
1920
|
+
index_count=len(self.intervals) - 1,
|
1921
|
+
bound_min=bound_min,
|
1922
|
+
bound_max=interval.end,
|
1923
|
+
bottom_point=self.bottom_point,
|
1924
|
+
top_point=self.top_point,
|
1925
|
+
pen=self.border_pen,
|
1926
|
+
movingPen=self.movingPen,
|
1927
|
+
hoverPen=self.hoverPen,
|
1928
|
+
movable=self.movable,
|
1929
|
+
)
|
1930
|
+
line.setZValue(30)
|
1931
|
+
line.setParentItem(self)
|
1932
|
+
# line.sigPositionChanged.connect(self._lineMoved)
|
1933
|
+
self.lines.append(line)
|
1934
|
+
bound_min = interval.begin
|
1935
|
+
self.refresh_tier()
|
1936
|
+
|
1937
|
+
def refresh_tier(self):
|
1938
|
+
self.regenerate_text_boxes()
|
1939
|
+
self.update()
|
1940
|
+
|
1941
|
+
def regenerate_text_boxes(self):
|
1722
1942
|
self.array = pg.Qt.internals.PrimitiveArray(QtCore.QRectF, 4)
|
1723
1943
|
self.selected_array = pg.Qt.internals.PrimitiveArray(QtCore.QRectF, 4)
|
1724
1944
|
self.array.resize(len(self.intervals))
|
1725
1945
|
self.selected = []
|
1726
|
-
self.settings = AnchorSettings()
|
1727
|
-
self.plot_theme = self.settings.plot_theme
|
1728
1946
|
memory = self.array.ndarray()
|
1729
|
-
self.anchor = pg.Point((0.5, 0.5))
|
1730
|
-
if self.word:
|
1731
|
-
self.plot_text_font = self.settings.font
|
1732
|
-
else:
|
1733
|
-
self.plot_text_font = self.settings.small_font
|
1734
1947
|
|
1735
1948
|
fm = QtGui.QFontMetrics(self.plot_text_font)
|
1736
|
-
for i, interval in enumerate(intervals):
|
1949
|
+
for i, interval in enumerate(self.intervals):
|
1737
1950
|
memory[i, 0] = interval.begin
|
1738
1951
|
memory[i, 2] = interval.end - interval.begin
|
1739
|
-
if interval.label not in self.parentItem().painter_path_cache
|
1952
|
+
if interval.label not in self.parentItem().painter_path_cache:
|
1740
1953
|
symbol = QtGui.QPainterPath()
|
1741
1954
|
|
1742
1955
|
symbol.addText(0, 0, self.plot_text_font, interval.label)
|
@@ -1747,46 +1960,28 @@ class IntervalTier(pg.GraphicsObject):
|
|
1747
1960
|
|
1748
1961
|
# translating
|
1749
1962
|
tr.translate(-br.x() - br.width() / 2.0, fm.height() / 2.0)
|
1750
|
-
self.parentItem().painter_path_cache[
|
1751
|
-
|
1752
|
-
memory[:, 1] = bottom_point
|
1753
|
-
memory[:, 3] = top_point - bottom_point
|
1754
|
-
self.top_point = top_point
|
1755
|
-
self.bottom_point = bottom_point
|
1756
|
-
self.selection_model = selection_model
|
1757
|
-
self.utterance = utterance
|
1963
|
+
self.parentItem().painter_path_cache[interval.label] = tr.map(symbol)
|
1758
1964
|
|
1759
|
-
|
1760
|
-
self.
|
1761
|
-
self.moving_line_color = self.plot_theme.moving_line_color
|
1762
|
-
|
1763
|
-
self.break_line_color = self.plot_theme.break_line_color
|
1764
|
-
self.text_color = self.plot_theme.text_color
|
1765
|
-
self.selected_interval_color = self.plot_theme.selected_interval_color
|
1766
|
-
self.highlight_interval_color = self.plot_theme.break_line_color
|
1767
|
-
self.highlight_text_color = self.plot_theme.background_color
|
1768
|
-
self.text_pen = pg.mkPen(self.text_color)
|
1769
|
-
self.text_brush = pg.mkBrush(self.text_color)
|
1770
|
-
self.highlight_text_pen = pg.mkPen(self.highlight_text_color)
|
1771
|
-
self.highlight_text_brush = pg.mkBrush(self.highlight_text_color)
|
1772
|
-
self.border_pen = pg.mkPen(self.break_line_color, width=1)
|
1773
|
-
self.border_pen.setCapStyle(QtCore.Qt.PenCapStyle.FlatCap)
|
1774
|
-
self.search_term = None
|
1775
|
-
self.search_regex = None
|
1965
|
+
memory[:, 1] = self.bottom_point
|
1966
|
+
memory[:, 3] = self.top_point - self.bottom_point
|
1776
1967
|
|
1777
1968
|
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
|
1778
1969
|
if e.button() == QtCore.Qt.MouseButton.LeftButton:
|
1970
|
+
if any(line.mouseHovering for line in self.lines):
|
1971
|
+
e.ignore()
|
1972
|
+
return
|
1779
1973
|
time = e.pos().x()
|
1974
|
+
|
1975
|
+
margin = 21 * self._cached_pixel_size[0]
|
1976
|
+
if time <= self.utterance.begin + margin or time >= self.utterance.end - margin:
|
1977
|
+
e.ignore()
|
1978
|
+
return
|
1780
1979
|
memory = self.array.ndarray()
|
1781
1980
|
if memory.shape[0] > 0:
|
1782
1981
|
index = np.searchsorted(memory[:, 0], time) - 1
|
1783
|
-
self.
|
1784
|
-
|
1785
|
-
)
|
1786
|
-
if self.word:
|
1787
|
-
self.highlightRequested.emit(
|
1788
|
-
TextFilterQuery(self.intervals[index].label, word=True)
|
1789
|
-
)
|
1982
|
+
interval = self.intervals[index]
|
1983
|
+
self.selection_model.select_audio(interval.begin, interval.end)
|
1984
|
+
self.highlightRequested.emit(TextFilterQuery(interval.label, word=True))
|
1790
1985
|
e.accept()
|
1791
1986
|
return
|
1792
1987
|
|
@@ -1814,9 +2009,10 @@ class IntervalTier(pg.GraphicsObject):
|
|
1814
2009
|
vb = self.getViewBox()
|
1815
2010
|
px = vb.viewPixelSize()
|
1816
2011
|
inst = self.array.instances()
|
2012
|
+
br = self.boundingRect()
|
1817
2013
|
painter.save()
|
1818
2014
|
painter.setPen(self.border_pen)
|
1819
|
-
painter.
|
2015
|
+
painter.drawRect(br)
|
1820
2016
|
painter.restore()
|
1821
2017
|
total_time = self.selection_model.max_time - self.selection_model.min_time
|
1822
2018
|
if self.selected:
|
@@ -1847,29 +2043,338 @@ class IntervalTier(pg.GraphicsObject):
|
|
1847
2043
|
painter.setPen(text_pen)
|
1848
2044
|
painter.setBrush(text_brush)
|
1849
2045
|
painter.translate(x, (self.top_point + self.bottom_point) / 2)
|
1850
|
-
path = self.parentItem().painter_path_cache[
|
2046
|
+
path = self.parentItem().painter_path_cache[interval.label]
|
1851
2047
|
painter.scale(px[0], -px[1])
|
1852
2048
|
painter.drawPath(path)
|
1853
2049
|
painter.restore()
|
1854
2050
|
|
1855
2051
|
def boundingRect(self):
|
1856
|
-
|
2052
|
+
br = QtCore.QRectF(
|
1857
2053
|
self.utterance.begin,
|
1858
2054
|
self.bottom_point,
|
1859
2055
|
self.utterance.end - self.utterance.begin,
|
1860
2056
|
abs(self.top_point - self.bottom_point),
|
1861
2057
|
)
|
2058
|
+
vb = self.getViewBox()
|
2059
|
+
self._cached_pixel_size = vb.viewPixelSize()
|
2060
|
+
if self._boundingRectCache != br:
|
2061
|
+
self._boundingRectCache = br
|
2062
|
+
self.prepareGeometryChange()
|
2063
|
+
return br
|
2064
|
+
|
2065
|
+
|
2066
|
+
class WordIntervalTier(IntervalTier):
|
2067
|
+
wordPronunciationChanged = QtCore.Signal(object, object)
|
2068
|
+
wordChanged = QtCore.Signal(object, object)
|
2069
|
+
|
2070
|
+
def __init__(
|
2071
|
+
self,
|
2072
|
+
parent,
|
2073
|
+
utterance: Utterance,
|
2074
|
+
intervals: typing.List[WordInterval],
|
2075
|
+
selection_model: FileSelectionModel,
|
2076
|
+
top_point,
|
2077
|
+
bottom_point,
|
2078
|
+
lookup=None,
|
2079
|
+
):
|
2080
|
+
super().__init__(
|
2081
|
+
parent, utterance, intervals, selection_model, top_point, bottom_point, lookup=lookup
|
2082
|
+
)
|
2083
|
+
|
2084
|
+
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
|
2085
|
+
if e.button() == QtCore.Qt.MouseButton.RightButton:
|
2086
|
+
time = e.pos().x()
|
2087
|
+
memory = self.array.ndarray()
|
2088
|
+
if memory.shape[0] > 0:
|
2089
|
+
index = np.searchsorted(memory[:, 0], time) - 1
|
2090
|
+
interval = self.intervals[index]
|
2091
|
+
self.selection_model.select_audio(interval.begin, interval.end)
|
2092
|
+
self.highlightRequested.emit(TextFilterQuery(interval.label, word=True))
|
2093
|
+
menu = self.construct_context_menu(interval)
|
2094
|
+
menu.exec_(e.screenPos())
|
2095
|
+
e.accept()
|
2096
|
+
return
|
2097
|
+
|
2098
|
+
return super().mousePressEvent(e)
|
2099
|
+
|
2100
|
+
def construct_context_menu(
|
2101
|
+
self, word_interval: typing.Union[WordInterval, ReferenceWordInterval]
|
2102
|
+
):
|
2103
|
+
menu = QtWidgets.QMenu()
|
2104
|
+
a = QtGui.QAction(menu)
|
2105
|
+
a.setText("Change word...")
|
2106
|
+
a.triggered.connect(lambda triggered, x=word_interval: self.change_word(x))
|
2107
|
+
menu.addAction(a)
|
2108
|
+
if isinstance(word_interval, WordInterval):
|
2109
|
+
change_pronunciation_menu = QtWidgets.QMenu("Change pronunciation")
|
2110
|
+
parent: UtteranceRegion = self.parentItem()
|
2111
|
+
pronunciations = (
|
2112
|
+
parent.corpus_model.session.query(Pronunciation)
|
2113
|
+
.filter(
|
2114
|
+
Pronunciation.word_id == word_interval.word_id,
|
2115
|
+
)
|
2116
|
+
.all()
|
2117
|
+
)
|
2118
|
+
for pron in pronunciations:
|
2119
|
+
if pron.id == word_interval.pronunciation_id:
|
2120
|
+
continue
|
2121
|
+
a = QtGui.QAction(menu)
|
2122
|
+
a.setText(pron.pronunciation)
|
2123
|
+
a.triggered.connect(
|
2124
|
+
lambda triggered, x=word_interval, y=pron: self.update_pronunciation(x, y)
|
2125
|
+
)
|
2126
|
+
change_pronunciation_menu.addAction(a)
|
2127
|
+
menu.addMenu(change_pronunciation_menu)
|
2128
|
+
change_pronunciation_menu.setStyleSheet(self.settings.menu_style_sheet)
|
2129
|
+
menu.setStyleSheet(self.settings.menu_style_sheet)
|
2130
|
+
return menu
|
2131
|
+
|
2132
|
+
def change_word(self, word_interval: typing.Union[WordInterval, ReferenceWordInterval]):
|
2133
|
+
from anchor.widgets import WordQueryDialog
|
2134
|
+
|
2135
|
+
parent: UtteranceRegion = self.parentItem()
|
2136
|
+
|
2137
|
+
dialog = WordQueryDialog(parent.corpus_model)
|
2138
|
+
if dialog.exec_():
|
2139
|
+
word = dialog.word_dropdown.current_text()
|
2140
|
+
self.wordChanged.emit(word_interval, word)
|
2141
|
+
|
2142
|
+
def update_pronunciation(
|
2143
|
+
self,
|
2144
|
+
word_interval: typing.Union[WordInterval, ReferenceWordInterval],
|
2145
|
+
pronunciation: Pronunciation,
|
2146
|
+
):
|
2147
|
+
self.wordPronunciationChanged.emit(word_interval, pronunciation)
|
2148
|
+
|
2149
|
+
|
2150
|
+
class PhoneIntervalTier(IntervalTier):
|
2151
|
+
draggingLine = QtCore.Signal(object)
|
2152
|
+
lineDragFinished = QtCore.Signal(object)
|
2153
|
+
phoneBoundaryChanged = QtCore.Signal(object, object, object)
|
2154
|
+
phoneIntervalChanged = QtCore.Signal(object, object)
|
2155
|
+
phoneIntervalInserted = QtCore.Signal(object, object, object, object, object, object)
|
2156
|
+
phoneIntervalDeleted = QtCore.Signal(object, object, object, object)
|
2157
|
+
deleteReferenceAlignments = QtCore.Signal()
|
2158
|
+
|
2159
|
+
def __init__(
|
2160
|
+
self,
|
2161
|
+
parent,
|
2162
|
+
utterance: Utterance,
|
2163
|
+
intervals: typing.List[PhoneInterval, ReferencePhoneInterval],
|
2164
|
+
selection_model: FileSelectionModel,
|
2165
|
+
top_point,
|
2166
|
+
bottom_point,
|
2167
|
+
lookup=None,
|
2168
|
+
):
|
2169
|
+
super().__init__(
|
2170
|
+
parent,
|
2171
|
+
utterance,
|
2172
|
+
intervals,
|
2173
|
+
selection_model,
|
2174
|
+
top_point,
|
2175
|
+
bottom_point,
|
2176
|
+
lookup=lookup,
|
2177
|
+
movable=True,
|
2178
|
+
)
|
2179
|
+
|
2180
|
+
def update_intervals(self, utterance):
|
2181
|
+
self.intervals = sorted(
|
2182
|
+
getattr(utterance, self.lookup, self.intervals), key=lambda x: x.begin
|
2183
|
+
)
|
2184
|
+
for line in self.lines:
|
2185
|
+
if line.scene() is not None:
|
2186
|
+
line.scene().removeItem(line)
|
2187
|
+
self.lines = []
|
2188
|
+
bound_min = self.utterance.begin
|
2189
|
+
for i, interval in enumerate(self.intervals):
|
2190
|
+
if i == 0:
|
2191
|
+
continue
|
2192
|
+
|
2193
|
+
line = IntervalLine(
|
2194
|
+
interval.begin,
|
2195
|
+
index=i - 1,
|
2196
|
+
index_count=len(self.intervals) - 1,
|
2197
|
+
bound_min=bound_min,
|
2198
|
+
bound_max=interval.end,
|
2199
|
+
bottom_point=self.bottom_point,
|
2200
|
+
top_point=self.top_point,
|
2201
|
+
pen=self.border_pen,
|
2202
|
+
movingPen=self.movingPen,
|
2203
|
+
hoverPen=self.hoverPen,
|
2204
|
+
movable=self.movable,
|
2205
|
+
)
|
2206
|
+
line.setZValue(30)
|
2207
|
+
line.setParentItem(self)
|
2208
|
+
line.sigPositionChangeFinished.connect(self.lineMoveFinished)
|
2209
|
+
# line.sigPositionChanged.connect(self._lineMoved)
|
2210
|
+
self.lines.append(line)
|
2211
|
+
bound_min = interval.begin
|
2212
|
+
line.sigPositionChanged.connect(self.draggingLine.emit)
|
2213
|
+
line.sigPositionChangeFinished.connect(self.lineDragFinished.emit)
|
2214
|
+
line.hoverChanged.connect(self.update_hover)
|
2215
|
+
self.refresh_tier()
|
2216
|
+
|
2217
|
+
def update_hover(self, hovered, time):
|
2218
|
+
if hovered:
|
2219
|
+
self.draggingLine.emit(time)
|
2220
|
+
else:
|
2221
|
+
self.lineDragFinished.emit(time)
|
2222
|
+
|
2223
|
+
def lineMoveFinished(self):
|
2224
|
+
sender: IntervalLine = self.sender()
|
2225
|
+
self.phoneBoundaryChanged.emit(
|
2226
|
+
self.intervals[sender.index], self.intervals[sender.index + 1], sender.pos().x()
|
2227
|
+
)
|
2228
|
+
if sender.index != 0:
|
2229
|
+
self.lines[sender.index - 1].bound_max = sender.pos().x()
|
2230
|
+
if sender.index != len(self.lines) - 1:
|
2231
|
+
self.lines[sender.index + 1].bound_min = sender.pos().x()
|
2232
|
+
self.regenerate_text_boxes()
|
2233
|
+
self.update()
|
2234
|
+
|
2235
|
+
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
|
2236
|
+
if e.button() == QtCore.Qt.MouseButton.RightButton:
|
2237
|
+
time = e.pos().x()
|
2238
|
+
memory = self.array.ndarray()
|
2239
|
+
if memory.shape[0] > 0:
|
2240
|
+
index = np.searchsorted(memory[:, 0], time) - 1
|
2241
|
+
interval = self.intervals[index]
|
2242
|
+
self.selection_model.select_audio(interval.begin, interval.end)
|
2243
|
+
self.highlightRequested.emit(TextFilterQuery(interval.label, word=True))
|
2244
|
+
initial = (time - interval.begin) < (interval.end - time)
|
2245
|
+
menu = self.construct_context_menu(index, interval, initial)
|
2246
|
+
menu.exec_(e.screenPos())
|
2247
|
+
e.accept()
|
2248
|
+
return
|
2249
|
+
|
2250
|
+
return super().mousePressEvent(e)
|
2251
|
+
|
2252
|
+
def update_phone(self, phone_interval, phone):
|
2253
|
+
self.phoneIntervalChanged.emit(phone_interval, phone)
|
2254
|
+
|
2255
|
+
def insert_phone_interval(self, index: int, initial: bool):
|
2256
|
+
previous_interval = None
|
2257
|
+
following_interval = None
|
2258
|
+
if initial:
|
2259
|
+
following_interval = self.intervals[index]
|
2260
|
+
word_interval_id = following_interval.word_interval_id
|
2261
|
+
if index > 0:
|
2262
|
+
previous_interval = self.intervals[index - 1]
|
2263
|
+
begin = following_interval.begin
|
2264
|
+
end = (following_interval.begin + following_interval.end) / 2
|
2265
|
+
else:
|
2266
|
+
previous_interval = self.intervals[index]
|
2267
|
+
word_interval_id = previous_interval.word_interval_id
|
2268
|
+
if index < len(self.intervals) - 1:
|
2269
|
+
following_interval = self.intervals[index + 1]
|
2270
|
+
begin = (previous_interval.begin + previous_interval.end) / 2
|
2271
|
+
end = previous_interval.end
|
2272
|
+
self.phoneIntervalInserted.emit(
|
2273
|
+
previous_interval, following_interval, word_interval_id, begin, end, self.lookup
|
2274
|
+
)
|
2275
|
+
|
2276
|
+
def insert_silence_interval(self, index: int, initial: bool):
|
2277
|
+
previous_interval = None
|
2278
|
+
following_interval = None
|
2279
|
+
if initial:
|
2280
|
+
following_interval = self.intervals[index]
|
2281
|
+
if index > 0:
|
2282
|
+
previous_interval = self.intervals[index - 1]
|
2283
|
+
begin = following_interval.begin
|
2284
|
+
end = (following_interval.begin + following_interval.end) / 2
|
2285
|
+
else:
|
2286
|
+
previous_interval = self.intervals[index]
|
2287
|
+
if index < len(self.intervals) - 1:
|
2288
|
+
following_interval = self.intervals[index + 1]
|
2289
|
+
begin = (previous_interval.begin + previous_interval.end) / 2
|
2290
|
+
end = previous_interval.end
|
2291
|
+
self.phoneIntervalInserted.emit(
|
2292
|
+
previous_interval, following_interval, None, begin, end, self.lookup
|
2293
|
+
)
|
2294
|
+
|
2295
|
+
def delete_phone_interval(self, index: int, initial: bool):
|
2296
|
+
previous_interval = None
|
2297
|
+
interval = self.intervals[index]
|
2298
|
+
following_interval = None
|
2299
|
+
if index > 0:
|
2300
|
+
previous_interval = self.intervals[index - 1]
|
2301
|
+
if index < len(self.intervals) - 1:
|
2302
|
+
following_interval = self.intervals[index + 1]
|
2303
|
+
if initial:
|
2304
|
+
time_point = interval.end
|
2305
|
+
else:
|
2306
|
+
time_point = interval.begin
|
2307
|
+
self.phoneIntervalDeleted.emit(interval, previous_interval, following_interval, time_point)
|
2308
|
+
|
2309
|
+
def delete_reference(self):
|
2310
|
+
self.deleteReferenceAlignments.emit()
|
2311
|
+
|
2312
|
+
def construct_context_menu(
|
2313
|
+
self,
|
2314
|
+
index,
|
2315
|
+
phone_interval: typing.Union[PhoneInterval, ReferencePhoneInterval],
|
2316
|
+
initial=True,
|
2317
|
+
):
|
2318
|
+
menu = QtWidgets.QMenu()
|
2319
|
+
change_phone_menu = QtWidgets.QMenu("Change phone")
|
2320
|
+
for phone_label, phone in sorted(
|
2321
|
+
self.parentItem().corpus_model.phones.items(), key=lambda x: x[0]
|
2322
|
+
):
|
2323
|
+
if phone_label == phone_interval.label:
|
2324
|
+
continue
|
2325
|
+
a = QtGui.QAction(menu)
|
2326
|
+
a.setText(phone_label)
|
2327
|
+
a.triggered.connect(
|
2328
|
+
lambda triggered, x=phone_interval, y=phone: self.update_phone(x, y)
|
2329
|
+
)
|
2330
|
+
change_phone_menu.addAction(a)
|
2331
|
+
menu.addMenu(change_phone_menu)
|
2332
|
+
a = QtGui.QAction(menu)
|
2333
|
+
a.setText("Insert silence/word")
|
2334
|
+
a.triggered.connect(
|
2335
|
+
lambda triggered, x=index, y=initial: self.insert_silence_interval(x, y)
|
2336
|
+
)
|
2337
|
+
menu.addAction(a)
|
2338
|
+
|
2339
|
+
a = QtGui.QAction(menu)
|
2340
|
+
a.setText("Insert interval")
|
2341
|
+
a.triggered.connect(lambda triggered, x=index, y=initial: self.insert_phone_interval(x, y))
|
2342
|
+
menu.addAction(a)
|
2343
|
+
|
2344
|
+
a = QtGui.QAction(menu)
|
2345
|
+
a.setText("Delete interval")
|
2346
|
+
a.triggered.connect(lambda triggered, x=index, y=initial: self.delete_phone_interval(x, y))
|
2347
|
+
menu.addAction(a)
|
2348
|
+
if self.lookup.startswith("reference"):
|
2349
|
+
a = QtGui.QAction(menu)
|
2350
|
+
a.setText("Delete reference alignments")
|
2351
|
+
a.triggered.connect(self.delete_reference)
|
2352
|
+
menu.addAction(a)
|
2353
|
+
change_phone_menu.setStyleSheet(self.settings.menu_style_sheet)
|
2354
|
+
menu.setStyleSheet(self.settings.menu_style_sheet)
|
2355
|
+
return menu
|
1862
2356
|
|
1863
2357
|
|
1864
2358
|
class UtteranceRegion(MfaRegion):
|
2359
|
+
phoneBoundaryChanged = QtCore.Signal(object, object, object, object)
|
2360
|
+
phoneIntervalChanged = QtCore.Signal(object, object, object)
|
2361
|
+
wordPronunciationChanged = QtCore.Signal(object, object, object)
|
2362
|
+
wordChanged = QtCore.Signal(object, object, object)
|
2363
|
+
phoneIntervalInserted = QtCore.Signal(object, object, object, object, object)
|
2364
|
+
phoneIntervalDeleted = QtCore.Signal(object, object, object, object, object)
|
2365
|
+
deleteReferenceAlignments = QtCore.Signal(object)
|
1865
2366
|
lookUpWord = QtCore.Signal(object)
|
1866
2367
|
createWord = QtCore.Signal(object)
|
1867
2368
|
transcribeRequested = QtCore.Signal(object)
|
2369
|
+
draggingLine = QtCore.Signal(object)
|
2370
|
+
lineDragFinished = QtCore.Signal(object)
|
2371
|
+
wordBoundariesChanged = QtCore.Signal(object, object)
|
2372
|
+
phoneTiersChanged = QtCore.Signal(object)
|
1868
2373
|
|
1869
2374
|
def __init__(
|
1870
2375
|
self,
|
1871
2376
|
parent,
|
1872
|
-
utterance:
|
2377
|
+
utterance: Utterance,
|
1873
2378
|
corpus_model: CorpusModel,
|
1874
2379
|
file_model: FileUtterancesModel,
|
1875
2380
|
dictionary_model: DictionaryTableModel,
|
@@ -1931,15 +2436,18 @@ class UtteranceRegion(MfaRegion):
|
|
1931
2436
|
**lineKwds,
|
1932
2437
|
),
|
1933
2438
|
]
|
1934
|
-
|
2439
|
+
self.snap_mode = False
|
2440
|
+
self.initial_line_moving = False
|
1935
2441
|
for line in self.lines:
|
1936
2442
|
line.setZValue(30)
|
1937
2443
|
line.setParentItem(self)
|
1938
2444
|
line.sigPositionChangeFinished.connect(self.lineMoveFinished)
|
2445
|
+
line.hoverChanged.connect(self.popup)
|
2446
|
+
line.sigPositionChanged.connect(self.draggingLine.emit)
|
2447
|
+
line.sigPositionChangeFinished.connect(self.lineDragFinished.emit)
|
2448
|
+
line.snapModeChanged.connect(self.update_snap_mode)
|
1939
2449
|
self.lines[0].sigPositionChanged.connect(self._line0Moved)
|
1940
2450
|
self.lines[1].sigPositionChanged.connect(self._line1Moved)
|
1941
|
-
self.lines[0].hoverChanged.connect(self.popup)
|
1942
|
-
self.lines[1].hoverChanged.connect(self.popup)
|
1943
2451
|
|
1944
2452
|
self.corpus_model.utteranceTextUpdated.connect(self.update_text_from_model)
|
1945
2453
|
self.original_text = self.item.text
|
@@ -1955,7 +2463,7 @@ class UtteranceRegion(MfaRegion):
|
|
1955
2463
|
search_term=search_term,
|
1956
2464
|
speaker_id=utterance.speaker_id,
|
1957
2465
|
)
|
1958
|
-
|
2466
|
+
self._painter_path_cache = {}
|
1959
2467
|
self.text_edit = self.text_item.text_edit
|
1960
2468
|
self.text_edit.gainedFocus.connect(self.select_self)
|
1961
2469
|
self.text_edit.menuRequested.connect(self.generate_text_edit_menu)
|
@@ -1972,6 +2480,7 @@ class UtteranceRegion(MfaRegion):
|
|
1972
2480
|
self.normalized_text = None
|
1973
2481
|
self.transcription_text = None
|
1974
2482
|
i = -1
|
2483
|
+
self.file_model.phoneTierChanged.connect(self.update_phone_tiers)
|
1975
2484
|
for tier_name, lookup in self.extra_tiers.items():
|
1976
2485
|
if not visible_tiers[tier_name]:
|
1977
2486
|
continue
|
@@ -2013,9 +2522,10 @@ class UtteranceRegion(MfaRegion):
|
|
2013
2522
|
self.transcription_text.text_edit.menuRequested.connect(
|
2014
2523
|
self.generate_text_edit_menu
|
2015
2524
|
)
|
2016
|
-
self.normalized_text
|
2017
|
-
self.
|
2018
|
-
|
2525
|
+
if self.normalized_text is not None:
|
2526
|
+
self.normalized_text.text_edit.textChanged.connect(
|
2527
|
+
self.update_transcription_highlight
|
2528
|
+
)
|
2019
2529
|
self.transcription_text.text_edit.textChanged.connect(
|
2020
2530
|
self.update_transcription_highlight
|
2021
2531
|
)
|
@@ -2040,35 +2550,51 @@ class UtteranceRegion(MfaRegion):
|
|
2040
2550
|
)
|
2041
2551
|
cmap.linearize()
|
2042
2552
|
if "phone_intervals" in lookup:
|
2043
|
-
|
2044
|
-
|
2045
|
-
|
2046
|
-
|
2047
|
-
min_confidence
|
2048
|
-
|
2049
|
-
max_confidence
|
2050
|
-
|
2553
|
+
if lookup != "reference_phone_intervals":
|
2554
|
+
for interval in intervals:
|
2555
|
+
if interval.confidence is None:
|
2556
|
+
continue
|
2557
|
+
if min_confidence is None or interval.confidence < min_confidence:
|
2558
|
+
min_confidence = interval.confidence
|
2559
|
+
if max_confidence is None or interval.confidence > max_confidence:
|
2560
|
+
max_confidence = interval.confidence
|
2561
|
+
interval_tier = PhoneIntervalTier(
|
2051
2562
|
self,
|
2052
2563
|
self.item,
|
2053
2564
|
intervals,
|
2054
2565
|
self.selection_model,
|
2055
2566
|
top_point=tier_top_point,
|
2056
2567
|
bottom_point=tier_bottom_point,
|
2057
|
-
|
2568
|
+
lookup=lookup,
|
2058
2569
|
)
|
2570
|
+
interval_tier.draggingLine.connect(self.draggingLine.emit)
|
2571
|
+
interval_tier.lineDragFinished.connect(self.lineDragFinished.emit)
|
2572
|
+
interval_tier.phoneBoundaryChanged.connect(self.change_phone_boundaries)
|
2573
|
+
interval_tier.phoneIntervalChanged.connect(self.change_phone_interval)
|
2574
|
+
interval_tier.phoneIntervalDeleted.connect(self.delete_phone_interval)
|
2575
|
+
interval_tier.phoneIntervalInserted.connect(self.insert_phone_interval)
|
2576
|
+
self.phoneTiersChanged.connect(interval_tier.update_intervals)
|
2577
|
+
if lookup.startswith("reference"):
|
2578
|
+
interval_tier.deleteReferenceAlignments.connect(
|
2579
|
+
self.delete_reference_alignments
|
2580
|
+
)
|
2059
2581
|
self.extra_tier_intervals[tier_name].append(interval_tier)
|
2060
2582
|
|
2061
2583
|
elif "word_intervals" in lookup:
|
2062
|
-
interval_tier =
|
2584
|
+
interval_tier = WordIntervalTier(
|
2063
2585
|
self,
|
2064
2586
|
self.item,
|
2065
2587
|
intervals,
|
2066
2588
|
self.selection_model,
|
2067
2589
|
top_point=tier_top_point,
|
2068
2590
|
bottom_point=tier_bottom_point,
|
2069
|
-
|
2591
|
+
lookup=lookup,
|
2070
2592
|
)
|
2593
|
+
self.wordBoundariesChanged.connect(interval_tier.refresh_boundaries)
|
2071
2594
|
interval_tier.highlightRequested.connect(self.set_search_term)
|
2595
|
+
interval_tier.wordPronunciationChanged.connect(self.change_word_pronunciation)
|
2596
|
+
interval_tier.wordChanged.connect(self.change_word)
|
2597
|
+
self.phoneTiersChanged.connect(interval_tier.update_intervals)
|
2072
2598
|
if self.transcription_text is not None:
|
2073
2599
|
interval_tier.highlightRequested.connect(
|
2074
2600
|
self.transcription_text.highlighter.setSearchTerm
|
@@ -2105,6 +2631,25 @@ class UtteranceRegion(MfaRegion):
|
|
2105
2631
|
self.show()
|
2106
2632
|
self.available_speakers = available_speakers
|
2107
2633
|
|
2634
|
+
def update_snap_mode(self, snap_mode):
|
2635
|
+
self.snap_mode = snap_mode
|
2636
|
+
|
2637
|
+
def _line0Moved(self):
|
2638
|
+
self.lineMoved(0)
|
2639
|
+
self.initial_line_moving = True
|
2640
|
+
|
2641
|
+
def _line1Moved(self):
|
2642
|
+
self.lineMoved(1)
|
2643
|
+
self.initial_line_moving = False
|
2644
|
+
|
2645
|
+
def delete_reference_alignments(self):
|
2646
|
+
self.deleteReferenceAlignments.emit(self.item)
|
2647
|
+
|
2648
|
+
def update_phone_tiers(self, utterance):
|
2649
|
+
if utterance.id != self.item.id:
|
2650
|
+
return
|
2651
|
+
self.phoneTiersChanged.emit(utterance)
|
2652
|
+
|
2108
2653
|
def update_transcription_highlight(self):
|
2109
2654
|
if self.item.normalized_text and self.item.transcription_text:
|
2110
2655
|
alignment = pairwise2.align.globalms(
|
@@ -2131,6 +2676,8 @@ class UtteranceRegion(MfaRegion):
|
|
2131
2676
|
|
2132
2677
|
@property
|
2133
2678
|
def painter_path_cache(self):
|
2679
|
+
if self.parentItem() is None:
|
2680
|
+
return self._painter_path_cache
|
2134
2681
|
return self.parentItem().painter_path_cache
|
2135
2682
|
|
2136
2683
|
def update_edit_fields(self):
|
@@ -2140,6 +2687,7 @@ class UtteranceRegion(MfaRegion):
|
|
2140
2687
|
self.normalized_text.text_item.update_times(begin, end)
|
2141
2688
|
if self.transcription_text is not None:
|
2142
2689
|
self.transcription_text.text_item.update_times(begin, end)
|
2690
|
+
self.phoneTiersChanged.emit(self.item)
|
2143
2691
|
|
2144
2692
|
def change_editing(self, editable: bool):
|
2145
2693
|
self.lines[0].movable = editable
|
@@ -2390,6 +2938,126 @@ class UtteranceRegion(MfaRegion):
|
|
2390
2938
|
self.text_edit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
2391
2939
|
self.update()
|
2392
2940
|
|
2941
|
+
def change_phone_boundaries(
|
2942
|
+
self, first_phone_interval, second_phone_interval, new_time: float
|
2943
|
+
):
|
2944
|
+
self.phoneBoundaryChanged.emit(
|
2945
|
+
self.item, first_phone_interval, second_phone_interval, new_time
|
2946
|
+
)
|
2947
|
+
if first_phone_interval.word_interval_id != second_phone_interval.word_interval_id:
|
2948
|
+
self.wordBoundariesChanged.emit(first_phone_interval.word_interval_id, new_time)
|
2949
|
+
|
2950
|
+
def change_phone_interval(self, phone_interval, new_phone_id):
|
2951
|
+
self.phoneIntervalChanged.emit(self.item, phone_interval, new_phone_id)
|
2952
|
+
|
2953
|
+
def change_word(self, word_interval, word):
|
2954
|
+
self.wordChanged.emit(self.item, word_interval, word)
|
2955
|
+
|
2956
|
+
def change_word_pronunciation(self, word_interval, pronunciation):
|
2957
|
+
self.wordPronunciationChanged.emit(self.item, word_interval, pronunciation)
|
2958
|
+
|
2959
|
+
def delete_phone_interval(self, interval, previous_interval, following_interval, time_point):
|
2960
|
+
self.phoneIntervalDeleted.emit(
|
2961
|
+
self.item, interval, previous_interval, following_interval, time_point
|
2962
|
+
)
|
2963
|
+
if (
|
2964
|
+
previous_interval is None
|
2965
|
+
or following_interval is None
|
2966
|
+
or previous_interval.word_interval_id != following_interval.word_interval_id
|
2967
|
+
):
|
2968
|
+
self.wordBoundariesChanged.emit(previous_interval.word_interval_id, time_point)
|
2969
|
+
|
2970
|
+
def insert_phone_interval(
|
2971
|
+
self,
|
2972
|
+
previous_interval,
|
2973
|
+
following_interval,
|
2974
|
+
word_interval_id,
|
2975
|
+
begin=None,
|
2976
|
+
end=None,
|
2977
|
+
lookup="phone_intervals",
|
2978
|
+
):
|
2979
|
+
if begin is None:
|
2980
|
+
begin = (
|
2981
|
+
(previous_interval.begin + previous_interval.end) / 2
|
2982
|
+
if previous_interval is not None
|
2983
|
+
else self.item.begin
|
2984
|
+
)
|
2985
|
+
if end is None:
|
2986
|
+
end = (
|
2987
|
+
(following_interval.begin + following_interval.end) / 2
|
2988
|
+
if following_interval is not None
|
2989
|
+
else self.item.end
|
2990
|
+
)
|
2991
|
+
inserting_word_interval = word_interval_id is None
|
2992
|
+
word_interval = None
|
2993
|
+
if inserting_word_interval:
|
2994
|
+
previous_word_interval_id = (
|
2995
|
+
previous_interval.word_interval_id if previous_interval is not None else None
|
2996
|
+
)
|
2997
|
+
following_word_interval_id = (
|
2998
|
+
following_interval.word_interval_id if following_interval is not None else None
|
2999
|
+
)
|
3000
|
+
at_word_boundary = previous_word_interval_id != following_word_interval_id
|
3001
|
+
if not at_word_boundary:
|
3002
|
+
return
|
3003
|
+
if not lookup.startswith("reference"):
|
3004
|
+
word_interval_id = self.corpus_model.corpus.get_next_primary_key(WordInterval)
|
3005
|
+
word = self.corpus_model.corpus.session.get(Word, 1)
|
3006
|
+
word_interval = WordInterval(
|
3007
|
+
id=word_interval_id,
|
3008
|
+
word=word,
|
3009
|
+
begin=begin,
|
3010
|
+
end=end,
|
3011
|
+
)
|
3012
|
+
else:
|
3013
|
+
word_interval_id = self.corpus_model.corpus.get_next_primary_key(
|
3014
|
+
ReferenceWordInterval
|
3015
|
+
)
|
3016
|
+
word = self.corpus_model.corpus.session.get(Word, 1)
|
3017
|
+
word_interval = ReferenceWordInterval(
|
3018
|
+
id=word_interval_id,
|
3019
|
+
word=word,
|
3020
|
+
begin=begin,
|
3021
|
+
end=end,
|
3022
|
+
)
|
3023
|
+
elif not lookup.startswith("reference"):
|
3024
|
+
for x in self.item.word_intervals:
|
3025
|
+
if x.id == word_interval_id:
|
3026
|
+
word_interval = x
|
3027
|
+
break
|
3028
|
+
else:
|
3029
|
+
for x in self.item.reference_word_intervals:
|
3030
|
+
if x.id == word_interval_id:
|
3031
|
+
word_interval = x
|
3032
|
+
break
|
3033
|
+
if not lookup.startswith("reference"):
|
3034
|
+
next_pk = self.corpus_model.corpus.get_next_primary_key(PhoneInterval)
|
3035
|
+
phone_interval = PhoneInterval(
|
3036
|
+
id=next_pk,
|
3037
|
+
phone=self.corpus_model.phones["sil"],
|
3038
|
+
begin=begin,
|
3039
|
+
end=end,
|
3040
|
+
word_interval=word_interval,
|
3041
|
+
word_interval_id=word_interval_id,
|
3042
|
+
)
|
3043
|
+
else:
|
3044
|
+
next_pk = self.corpus_model.corpus.get_next_primary_key(ReferencePhoneInterval)
|
3045
|
+
phone_interval = ReferencePhoneInterval(
|
3046
|
+
id=next_pk,
|
3047
|
+
phone=self.corpus_model.phones["sil"],
|
3048
|
+
begin=begin,
|
3049
|
+
end=end,
|
3050
|
+
word_interval=word_interval,
|
3051
|
+
word_interval_id=word_interval_id,
|
3052
|
+
)
|
3053
|
+
self.phoneIntervalInserted.emit(
|
3054
|
+
self.item,
|
3055
|
+
phone_interval,
|
3056
|
+
previous_interval,
|
3057
|
+
following_interval,
|
3058
|
+
word_interval if inserting_word_interval else None,
|
3059
|
+
)
|
3060
|
+
|
2393
3061
|
def save_changes(self):
|
2394
3062
|
text = self.text_edit.toPlainText()
|
2395
3063
|
if self.original_text == text:
|
@@ -2830,7 +3498,7 @@ class SpeakerTier(pg.GraphicsObject):
|
|
2830
3498
|
self.selection_model.selectionChanged.connect(self.update_select)
|
2831
3499
|
self.selection_model.model().utterancesReady.connect(self.refresh)
|
2832
3500
|
self.available_speakers = {}
|
2833
|
-
self.painter_path_cache = {
|
3501
|
+
self.painter_path_cache = {}
|
2834
3502
|
|
2835
3503
|
def wheelEvent(self, ev):
|
2836
3504
|
self.receivedWheelEvent.emit(ev)
|
@@ -2953,10 +3621,8 @@ class SpeakerTier(pg.GraphicsObject):
|
|
2953
3621
|
)
|
2954
3622
|
reg.sigRegionChanged.connect(self.check_utterance_bounds)
|
2955
3623
|
reg.sigRegionChangeFinished.connect(self.update_utterance)
|
2956
|
-
reg.
|
2957
|
-
reg.
|
2958
|
-
reg.lines[1].sigPositionChanged.connect(self.draggingLine.emit)
|
2959
|
-
reg.lines[0].sigPositionChangeFinished.connect(self.lineDragFinished.emit)
|
3624
|
+
reg.draggingLine.connect(self.draggingLine.emit)
|
3625
|
+
reg.sigRegionChangeFinished.connect(self.lineDragFinished.emit)
|
2960
3626
|
reg.undoRequested.connect(self.corpus_model.undoRequested.emit)
|
2961
3627
|
reg.undoRequested.connect(self.corpus_model.undoRequested.emit)
|
2962
3628
|
reg.redoRequested.connect(self.corpus_model.redoRequested.emit)
|
@@ -2964,12 +3630,58 @@ class SpeakerTier(pg.GraphicsObject):
|
|
2964
3630
|
reg.audioSelected.connect(self.selection_model.select_audio)
|
2965
3631
|
reg.viewRequested.connect(self.selection_model.set_view_times)
|
2966
3632
|
reg.textEdited.connect(self.update_utterance_text)
|
3633
|
+
reg.phoneBoundaryChanged.connect(self.update_phone_boundaries)
|
3634
|
+
reg.phoneIntervalChanged.connect(self.update_phone_interval)
|
3635
|
+
reg.wordPronunciationChanged.connect(self.update_word_pronunciation)
|
3636
|
+
reg.wordChanged.connect(self.update_word)
|
3637
|
+
reg.phoneIntervalInserted.connect(self.insert_phone_interval)
|
3638
|
+
reg.phoneIntervalDeleted.connect(self.delete_phone_interval)
|
3639
|
+
reg.deleteReferenceAlignments.connect(self.delete_reference_alignments)
|
2967
3640
|
reg.transcribeRequested.connect(self.corpus_model.transcribeRequested.emit)
|
2968
3641
|
reg.selectRequested.connect(self.selection_model.update_select)
|
2969
3642
|
self.visible_utterances[u.id] = reg
|
2970
3643
|
|
2971
3644
|
self.show()
|
2972
3645
|
|
3646
|
+
def delete_reference_alignments(self, utterance: Utterance):
|
3647
|
+
self.selection_model.model().delete_reference_alignments(utterance)
|
3648
|
+
|
3649
|
+
def update_phone_boundaries(
|
3650
|
+
self, utterance: Utterance, first_phone_interval, second_phone_interval, new_time: float
|
3651
|
+
):
|
3652
|
+
self.selection_model.model().update_phone_boundaries(
|
3653
|
+
utterance, first_phone_interval, second_phone_interval, new_time
|
3654
|
+
)
|
3655
|
+
|
3656
|
+
def update_phone_interval(self, utterance: Utterance, phone_interval, phone_id):
|
3657
|
+
self.selection_model.model().update_phone_interval(utterance, phone_interval, phone_id)
|
3658
|
+
|
3659
|
+
def update_word_pronunciation(
|
3660
|
+
self, utterance: Utterance, word_interval: WordInterval, pronunciation: Pronunciation
|
3661
|
+
):
|
3662
|
+
self.selection_model.model().update_word_pronunciation(
|
3663
|
+
utterance, word_interval, pronunciation
|
3664
|
+
)
|
3665
|
+
|
3666
|
+
def update_word(
|
3667
|
+
self, utterance: Utterance, word_interval: WordInterval, word: typing.Union[Word, str]
|
3668
|
+
):
|
3669
|
+
self.selection_model.model().update_word(utterance, word_interval, word)
|
3670
|
+
|
3671
|
+
def insert_phone_interval(
|
3672
|
+
self, utterance: Utterance, interval, previous_interval, following_interval, word_interval
|
3673
|
+
):
|
3674
|
+
self.selection_model.model().insert_phone_interval(
|
3675
|
+
utterance, interval, previous_interval, following_interval, word_interval
|
3676
|
+
)
|
3677
|
+
|
3678
|
+
def delete_phone_interval(
|
3679
|
+
self, utterance: Utterance, interval, previous_interval, following_interval, time_point
|
3680
|
+
):
|
3681
|
+
self.selection_model.model().delete_phone_interval(
|
3682
|
+
utterance, interval, previous_interval, following_interval, time_point
|
3683
|
+
)
|
3684
|
+
|
2973
3685
|
def update_utterance_text(self, utterance, new_text):
|
2974
3686
|
self.selection_model.model().update_utterance_text(utterance, text=new_text)
|
2975
3687
|
|
@@ -2982,7 +3694,7 @@ class SpeakerTier(pg.GraphicsObject):
|
|
2982
3694
|
r.setSelected(False)
|
2983
3695
|
|
2984
3696
|
def check_utterance_bounds(self):
|
2985
|
-
reg = self.sender()
|
3697
|
+
reg: UtteranceRegion = self.sender()
|
2986
3698
|
with QtCore.QSignalBlocker(reg):
|
2987
3699
|
beg, end = reg.getRegion()
|
2988
3700
|
if self.settings.right_to_left:
|
@@ -3005,16 +3717,35 @@ class SpeakerTier(pg.GraphicsObject):
|
|
3005
3717
|
):
|
3006
3718
|
reg.setRegion([beg, self.selection_model.model().file.duration])
|
3007
3719
|
return
|
3008
|
-
|
3720
|
+
prev_r = None
|
3721
|
+
for r in sorted(self.visible_utterances.values(), key=lambda x: x.item_min):
|
3009
3722
|
if r.item.id == reg.item.id:
|
3723
|
+
if reg.initial_line_moving and reg.snap_mode and prev_r is not None:
|
3724
|
+
other_begin, other_end = prev_r.getRegion()
|
3725
|
+
prev_r.setRegion([other_begin, beg])
|
3726
|
+
break
|
3010
3727
|
continue
|
3011
3728
|
other_begin, other_end = r.getRegion()
|
3012
3729
|
if other_begin <= beg < other_end or beg <= other_begin < other_end < end:
|
3013
|
-
reg.
|
3730
|
+
if reg.initial_line_moving and reg.snap_mode:
|
3731
|
+
r.setRegion([other_begin, beg])
|
3732
|
+
else:
|
3733
|
+
reg.setRegion([other_end, end])
|
3014
3734
|
break
|
3015
3735
|
if other_begin < end <= other_end or end > other_begin > other_end > beg:
|
3016
|
-
|
3736
|
+
if (
|
3737
|
+
False
|
3738
|
+
and not reg.initial_line_moving
|
3739
|
+
and reg.snap_mode
|
3740
|
+
and prev_r is not None
|
3741
|
+
and prev_r.item.id == reg.item.id
|
3742
|
+
):
|
3743
|
+
r.setRegion([end, other_end])
|
3744
|
+
else:
|
3745
|
+
reg.setRegion([beg, other_begin])
|
3017
3746
|
break
|
3747
|
+
prev_r = r
|
3748
|
+
|
3018
3749
|
reg.update_edit_fields()
|
3019
3750
|
reg.select_self()
|
3020
3751
|
reg.update()
|