Anchor-annotator 0.1.0__py3-none-any.whl → 0.2.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/plot.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import functools
3
4
  import logging
4
5
  import os.path
5
6
  import re
@@ -10,6 +11,7 @@ import numpy as np
10
11
  import pyqtgraph as pg
11
12
  import sqlalchemy
12
13
  from Bio import pairwise2
14
+ from line_profiler_pycharm import profile
13
15
  from montreal_forced_aligner.data import CtmInterval
14
16
  from montreal_forced_aligner.db import Speaker, Utterance
15
17
  from PySide6 import QtCore, QtGui, QtWidgets
@@ -19,6 +21,8 @@ from anchor.models import (
19
21
  CorpusModel,
20
22
  CorpusSelectionModel,
21
23
  DictionaryTableModel,
24
+ FileSelectionModel,
25
+ FileUtterancesModel,
22
26
  SpeakerModel,
23
27
  TextFilterQuery,
24
28
  )
@@ -408,6 +412,31 @@ class SpeakerTierItem(pg.PlotItem):
408
412
  self.setMenuEnabled(False)
409
413
  self.hideButtons()
410
414
 
415
+ def contextMenuEvent(self, event: QtWidgets.QGraphicsSceneContextMenuEvent):
416
+ vb = self.getViewBox()
417
+ item = self.items[0]
418
+ x = vb.mapFromItemToView(self, event.pos()).x()
419
+ begin = max(x - 0.5, 0)
420
+ end = min(x + 0.5, item.selection_model.model().file.duration)
421
+ for x in item.visible_utterances.values():
422
+ if begin >= x.item_min and end <= x.item_max:
423
+ event.accept()
424
+ return
425
+ if begin < x.item_max and begin > x.item_max:
426
+ begin = x.item_max
427
+ if end > x.item_min and end < x.item_min:
428
+ end = x.item_min
429
+ break
430
+ if end - begin > 0.001:
431
+ menu = QtWidgets.QMenu()
432
+
433
+ a = QtGui.QAction(menu)
434
+ a.setText("Create utterance")
435
+ a.triggered.connect(functools.partial(item.create_utterance, begin=begin, end=end))
436
+ menu.addAction(a)
437
+ menu.setStyleSheet(item.settings.menu_style_sheet)
438
+ menu.exec_(event.screenPos())
439
+
411
440
 
412
441
  class UtteranceView(QtWidgets.QWidget):
413
442
  undoRequested = QtCore.Signal()
@@ -418,23 +447,15 @@ class UtteranceView(QtWidgets.QWidget):
418
447
  super().__init__(*args)
419
448
  self.settings = AnchorSettings()
420
449
  self.corpus_model: typing.Optional[CorpusModel] = None
450
+ self.file_model: typing.Optional[FileUtterancesModel] = None
421
451
  self.dictionary_model: typing.Optional[DictionaryTableModel] = None
422
- self.selection_model: typing.Optional[CorpusSelectionModel] = None
452
+ self.selection_model: typing.Optional[FileSelectionModel] = None
423
453
  layout = QtWidgets.QVBoxLayout()
424
454
  self.bottom_point = 0
425
455
  self.top_point = 8
426
456
  self.height = self.top_point - self.bottom_point
427
457
  self.separator_point = (self.height / 2) + self.bottom_point
428
- self.waveform_worker = workers.WaveformWorker()
429
- self.auto_waveform_worker = workers.AutoWaveformWorker()
430
- self.spectrogram_worker = workers.SpectrogramWorker()
431
- self.pitch_track_worker = workers.PitchWorker()
432
- self.speaker_tier_worker = workers.SpeakerTierWorker()
433
- self.waveform_worker.signals.result.connect(self.finalize_loading_wave_form)
434
- self.auto_waveform_worker.signals.result.connect(self.finalize_loading_auto_wave_form)
435
- self.spectrogram_worker.signals.result.connect(self.finalize_loading_spectrogram)
436
- self.pitch_track_worker.signals.result.connect(self.finalize_loading_pitch_track)
437
- self.speaker_tier_worker.signals.result.connect(self.finalize_loading_utterances)
458
+
438
459
  # self.break_line.setZValue(30)
439
460
  self.audio_layout = pg.GraphicsLayoutWidget()
440
461
  self.audio_layout.centralWidget.layout.setContentsMargins(0, 0, 0, 0)
@@ -454,7 +475,9 @@ class UtteranceView(QtWidgets.QWidget):
454
475
  self.speaker_tier_layout.centralWidget.layout.setContentsMargins(0, 0, 0, 0)
455
476
  self.speaker_tier_layout.centralWidget.layout.setSpacing(0)
456
477
  self.speaker_tiers: dict[SpeakerTier] = {}
478
+ self.speaker_tier_items = {}
457
479
  self.search_term = None
480
+ self.default_speaker_id = None
458
481
  self.extra_tiers = {}
459
482
  self.tier_scroll_area = QtWidgets.QScrollArea()
460
483
  self.audio_scroll_area = QtWidgets.QScrollArea()
@@ -476,22 +499,16 @@ class UtteranceView(QtWidgets.QWidget):
476
499
  scroll_layout.setSpacing(0)
477
500
  self.setLayout(layout)
478
501
 
479
- def clean_up_for_close(self):
480
- self.spectrogram_worker.stop()
481
- self.pitch_track_worker.stop()
482
- self.waveform_worker.stop()
483
- self.auto_waveform_worker.stop()
484
- self.speaker_tier_worker.stop()
485
-
486
502
  def set_models(
487
503
  self,
488
504
  corpus_model: CorpusModel,
489
- selection_model: CorpusSelectionModel,
505
+ file_model: FileUtterancesModel,
506
+ selection_model: FileSelectionModel,
490
507
  dictionary_model: DictionaryTableModel,
491
508
  ):
492
509
  self.corpus_model = corpus_model
510
+ self.file_model = file_model
493
511
  self.corpus_model.corpusLoaded.connect(self.set_extra_tiers)
494
- self.corpus_model.refreshTiers.connect(self.set_up_new_file)
495
512
  self.selection_model = selection_model
496
513
  self.dictionary_model = dictionary_model
497
514
  for t in self.speaker_tiers.values():
@@ -499,55 +516,61 @@ class UtteranceView(QtWidgets.QWidget):
499
516
  self.audio_plot.set_models(self.selection_model)
500
517
  self.selection_model.viewChanged.connect(self.update_plot)
501
518
  # self.corpus_model.utteranceTextUpdated.connect(self.refresh_utterance_text)
502
- self.selection_model.fileChanged.connect(self.set_up_new_file)
503
- self.selection_model.channelChanged.connect(self.update_channel)
504
519
  self.selection_model.resetView.connect(self.reset_plot)
505
-
506
- def finalize_loading_utterances(self, results):
507
- utterances, file_id = results
508
- if (
509
- self.selection_model.current_file is None
510
- or file_id != self.selection_model.current_file.id
511
- ):
520
+ self.file_model.utterancesReady.connect(self.finalize_loading_utterances)
521
+ self.selection_model.spectrogramReady.connect(self.finalize_loading_spectrogram)
522
+ self.selection_model.pitchTrackReady.connect(self.finalize_loading_pitch_track)
523
+ self.selection_model.waveformReady.connect(self.finalize_loading_auto_wave_form)
524
+ self.selection_model.speakerRequested.connect(self.set_default_speaker)
525
+ self.file_model.selectionRequested.connect(self.finalize_loading_utterances)
526
+
527
+ def finalize_loading_utterances(self):
528
+ if self.file_model.file is None:
512
529
  return
530
+ scroll_to = None
531
+
513
532
  self.speaker_tiers = {}
514
533
  self.speaker_tier_items = {}
515
534
  self.speaker_tier_layout.clear()
516
535
  available_speakers = {}
517
- for u in utterances:
518
- if u.speaker_id not in self.speaker_tiers:
519
- speaker_name = self.corpus_model.get_speaker_name(u.speaker_id)
520
- tier = SpeakerTier(
521
- self.bottom_point,
522
- self.separator_point,
523
- u.speaker_id,
524
- speaker_name,
525
- search_term=self.search_term,
526
- )
527
- tier.dragFinished.connect(self.update_selected_speaker)
528
- tier.draggingLine.connect(self.audio_plot.update_drag_line)
529
- tier.lineDragFinished.connect(self.audio_plot.hide_drag_line)
530
- tier.receivedWheelEvent.connect(self.audio_plot.wheelEvent)
531
- tier.set_models(self.corpus_model, self.selection_model, self.dictionary_model)
532
- tier.set_extra_tiers(self.extra_tiers)
533
- tier.setZValue(30)
534
- available_speakers[speaker_name] = u.speaker_id
535
- self.speaker_tiers[u.speaker_id] = tier
536
- self.speaker_tiers[u.speaker_id].utterances.append(u)
536
+ speaker_tier_height = self.separator_point - self.bottom_point
537
+ for i, speaker_id in enumerate(self.file_model.speakers):
538
+ speaker_name = self.corpus_model.get_speaker_name(speaker_id)
539
+ top_point = i * speaker_tier_height
540
+ bottom_point = top_point - speaker_tier_height
541
+ tier = SpeakerTier(
542
+ top_point,
543
+ bottom_point,
544
+ speaker_id,
545
+ speaker_name,
546
+ self.corpus_model,
547
+ self.file_model,
548
+ self.selection_model,
549
+ self.dictionary_model,
550
+ search_term=self.search_term,
551
+ )
552
+ tier.draggingLine.connect(self.audio_plot.update_drag_line)
553
+ tier.lineDragFinished.connect(self.audio_plot.hide_drag_line)
554
+ tier.receivedWheelEvent.connect(self.audio_plot.wheelEvent)
555
+ tier.set_extra_tiers(self.extra_tiers)
556
+ tier.setZValue(30)
557
+ available_speakers[speaker_name] = speaker_id
558
+ self.speaker_tiers[speaker_id] = tier
537
559
  for i, (key, tier) in enumerate(self.speaker_tiers.items()):
538
- tier.set_speaker_index(0, 1)
539
560
  tier.set_available_speakers(available_speakers)
540
561
  tier.refresh()
541
- tier_item = SpeakerTierItem(self.bottom_point, self.separator_point)
562
+ top_point = i * speaker_tier_height
563
+ bottom_point = top_point - speaker_tier_height
564
+ tier_item = SpeakerTierItem(top_point, bottom_point)
542
565
  tier_item.setRange(
543
566
  xRange=[self.selection_model.plot_min, self.selection_model.plot_max]
544
567
  )
545
568
  tier_item.addItem(tier)
546
569
  self.speaker_tier_items[key] = tier_item
547
570
  self.speaker_tier_layout.addItem(tier_item, i, 0)
571
+ if tier.speaker_id == self.default_speaker_id:
572
+ scroll_to = i
548
573
  row_height = self.audio_plot_item.height()
549
- if len(self.speaker_tiers) > 1 and len(self.extra_tiers) < 2:
550
- row_height = int(row_height / 2)
551
574
  self.speaker_tier_layout.setFixedHeight(len(self.speaker_tiers) * row_height)
552
575
  if len(self.speaker_tiers) > 1:
553
576
  self.tier_scroll_area.verticalScrollBar().setSingleStep(row_height)
@@ -562,91 +585,73 @@ class UtteranceView(QtWidgets.QWidget):
562
585
  self.audio_layout.centralWidget.layout.setContentsMargins(
563
586
  0, 0, self.settings.scroll_bar_height, 0
564
587
  )
588
+ if scroll_to is not None:
589
+ # self.tier_scroll_area.scrollContentsBy(0, scroll_to * tier_height)
590
+ self.tier_scroll_area.verticalScrollBar().setValue(
591
+ scroll_to * self.tier_scroll_area.height()
592
+ )
593
+ self.default_speaker_id = None
565
594
  else:
566
595
  self.audio_layout.centralWidget.layout.setContentsMargins(0, 0, 0, 0)
567
596
  self.tier_scroll_area.setVerticalScrollBarPolicy(
568
597
  QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
569
598
  )
570
599
 
571
- def finalize_loading_wave_form(self, results):
572
- y, file_path = results
573
- if (
574
- self.selection_model.current_file is None
575
- or file_path != self.selection_model.current_file.sound_file.sound_file_path
576
- ):
577
- return
578
- self.audio_plot.wave_form.y = y
579
- self.get_latest_waveform()
600
+ def set_default_speaker(self, speaker_id):
601
+ self.default_speaker_id = speaker_id
580
602
 
581
- def finalize_loading_spectrogram(self, results):
582
- stft, channel, begin, end, min_db, max_db = results
583
- if self.settings.right_to_left:
584
- stft = np.flip(stft, 1)
585
- begin, end = -end, -begin
586
- if begin != self.selection_model.plot_min or end != self.selection_model.plot_max:
603
+ def finalize_loading_spectrogram(self):
604
+ self.audio_plot.spectrogram.hide()
605
+ if self.selection_model.spectrogram is None:
606
+ self.audio_plot.spectrogram.clear()
587
607
  return
588
- self.audio_plot.spectrogram.setData(stft, channel, begin, end, min_db, max_db)
608
+ self.audio_plot.spectrogram.setData(
609
+ self.selection_model.spectrogram,
610
+ self.selection_model.selected_channel,
611
+ self.selection_model.plot_min,
612
+ self.selection_model.plot_max,
613
+ self.selection_model.min_db,
614
+ self.selection_model.max_db,
615
+ )
589
616
 
590
- def finalize_loading_pitch_track(self, results):
591
- pitch_track, voicing_track, channel, begin, end, min_f0, max_f0 = results
592
- if self.settings.right_to_left:
593
- pitch_track = np.flip(pitch_track, 0)
594
- begin, end = -end, -begin
595
- if begin != self.selection_model.plot_min or end != self.selection_model.plot_max:
596
- return
597
- if pitch_track is None:
617
+ def finalize_loading_pitch_track(self):
618
+ self.audio_plot.pitch_track.hide()
619
+ self.audio_plot.pitch_track.clear()
620
+ if self.selection_model.pitch_track_y is None:
598
621
  return
599
- x = np.linspace(
600
- start=self.selection_model.plot_min,
601
- stop=self.selection_model.plot_max,
602
- num=pitch_track.shape[0],
622
+ self.audio_plot.pitch_track.setData(
623
+ x=self.selection_model.pitch_track_x,
624
+ y=self.selection_model.pitch_track_y,
625
+ connect="finite",
626
+ )
627
+ self.audio_plot.pitch_track.set_range(
628
+ self.settings.value(self.settings.PITCH_MIN_F0),
629
+ self.settings.value(self.settings.PITCH_MAX_F0),
630
+ self.selection_model.plot_max,
603
631
  )
604
- self.audio_plot.pitch_track.hide()
605
- self.audio_plot.pitch_track.setData(x=x, y=pitch_track, connect="finite")
606
- self.audio_plot.pitch_track.set_range(min_f0, max_f0, end)
607
632
  self.audio_plot.pitch_track.show()
608
633
 
609
- def finalize_loading_auto_wave_form(self, results):
610
- y, begin, end, channel = results
611
- if self.settings.right_to_left:
612
- y = np.flip(y, 0)
613
- begin, end = -end, -begin
614
- if begin != self.selection_model.plot_min or end != self.selection_model.plot_max:
634
+ def finalize_loading_auto_wave_form(self):
635
+ self.audio_plot.wave_form.hide()
636
+ if self.selection_model.waveform_y is None:
615
637
  return
616
- x = np.linspace(
617
- start=self.selection_model.plot_min, stop=self.selection_model.plot_max, num=y.shape[0]
638
+ self.audio_plot_item.setRange(
639
+ xRange=[self.selection_model.plot_min, self.selection_model.plot_max]
640
+ )
641
+ self.audio_plot.update_plot()
642
+ self.audio_plot.wave_form.setData(
643
+ x=self.selection_model.waveform_x, y=self.selection_model.waveform_y
618
644
  )
619
- # self.audio_plot.wave_form.hide()
620
- self.audio_plot.wave_form.setData(x=x, y=y)
621
645
  self.audio_plot.wave_form.show()
622
646
 
623
- def get_utterances(self):
624
- for tier in self.speaker_tiers.values():
625
- tier.reset_tier()
626
- self.speaker_tier_layout.removeItem(tier)
627
- if self.selection_model.current_file is None:
628
- return
629
- self.speaker_tier_worker.stop()
630
- self.speaker_tier_worker.set_params(self.selection_model.current_file.id)
631
- self.speaker_tier_worker.start()
632
-
633
647
  def set_extra_tiers(self):
634
- self.speaker_tier_worker.query_alignment = False
635
- self.speaker_tier_worker.session = self.corpus_model.session
636
648
  self.extra_tiers = {}
637
- visible_tiers = self.settings.visible_tiers
638
649
  self.extra_tiers["Normalized text"] = "normalized_text"
639
650
  if self.corpus_model.has_alignments and "Words" not in self.extra_tiers:
640
651
  self.extra_tiers["Words"] = "aligned_word_intervals"
641
- if visible_tiers.get("Words", True):
642
- self.speaker_tier_worker.query_alignment = True
643
652
  self.extra_tiers["Phones"] = "aligned_phone_intervals"
644
- if visible_tiers.get("Phones", True):
645
- self.speaker_tier_worker.query_alignment = True
646
653
  if self.corpus_model.has_reference_alignments and "Reference" not in self.extra_tiers:
647
654
  self.extra_tiers["Reference"] = "reference_phone_intervals"
648
- if visible_tiers.get("Reference", True):
649
- self.speaker_tier_worker.query_alignment = True
650
655
  if (
651
656
  self.corpus_model.has_transcribed_alignments
652
657
  and "Transcription" not in self.extra_tiers
@@ -654,10 +659,6 @@ class UtteranceView(QtWidgets.QWidget):
654
659
  self.extra_tiers["Transcription"] = "transcription_text"
655
660
  self.extra_tiers["Transcribed words"] = "transcribed_word_intervals"
656
661
  self.extra_tiers["Transcribed phones"] = "transcribed_phone_intervals"
657
- if visible_tiers.get("Transcribed words", True):
658
- self.speaker_tier_worker.query_alignment = True
659
- if visible_tiers.get("Transcribed phones", True):
660
- self.speaker_tier_worker.query_alignment = True
661
662
  if (
662
663
  self.corpus_model.has_per_speaker_transcribed_alignments
663
664
  and "Transcription" not in self.extra_tiers
@@ -665,29 +666,6 @@ class UtteranceView(QtWidgets.QWidget):
665
666
  self.extra_tiers["Transcription"] = "transcription_text"
666
667
  self.extra_tiers["Transcribed words"] = "per_speaker_transcribed_word_intervals"
667
668
  self.extra_tiers["Transcribed phones"] = "per_speaker_transcribed_phone_intervals"
668
- if visible_tiers.get("Transcribed words", True):
669
- self.speaker_tier_worker.query_alignment = True
670
- if visible_tiers.get("Transcribed phones", True):
671
- self.speaker_tier_worker.query_alignment = True
672
-
673
- def update_channel(self):
674
- self.get_latest_waveform()
675
-
676
- def set_up_new_file(self, *args):
677
- self.audio_plot.spectrogram.cached_begin = None
678
- self.audio_plot.spectrogram.cached_end = None
679
- self.audio_plot.wave_form.y = None
680
- for t in self.speaker_tiers.values():
681
- t.visible_utterances = {}
682
- self.speaker_tiers = {}
683
- if self.selection_model.current_file is None:
684
- return
685
- self.get_utterances()
686
- self.waveform_worker.stop()
687
- self.waveform_worker.set_params(
688
- self.selection_model.current_file.sound_file.sound_file_path
689
- )
690
- self.waveform_worker.start()
691
669
 
692
670
  def set_search_term(self):
693
671
  term = self.corpus_model.text_filter
@@ -704,74 +682,23 @@ class UtteranceView(QtWidgets.QWidget):
704
682
  def draw_text_grid(self):
705
683
  scroll_to = None
706
684
  for i, (key, tier) in enumerate(self.speaker_tiers.items()):
685
+ self.speaker_tier_items[key].hide()
707
686
  tier.refresh()
708
- if tier.has_visible_utterances and scroll_to is None:
687
+ if tier.speaker_id == self.default_speaker_id:
709
688
  scroll_to = i
710
689
  tier_height = self.speaker_tier_items[key].height()
711
690
  self.speaker_tier_items[key].setRange(
712
691
  xRange=[self.selection_model.plot_min, self.selection_model.plot_max]
713
692
  )
693
+ self.speaker_tier_items[key].show()
714
694
  if scroll_to is not None:
715
- self.tier_scroll_area.scrollContentsBy(0, scroll_to * tier_height)
695
+ self.tier_scroll_area.verticalScrollBar().setValue(scroll_to * tier_height)
696
+ self.default_speaker_id = None
716
697
 
717
698
  def update_show_speakers(self, state):
718
699
  self.show_all_speakers = state > 0
719
700
  self.update_plot()
720
701
 
721
- def get_latest_waveform(self):
722
- if self.audio_plot.wave_form.y is None:
723
- return
724
- self.audio_plot.wave_form.hide()
725
- # self.audio_plot.spectrogram.hide()
726
- self.audio_plot.pitch_track.hide()
727
- begin_samp = int(
728
- self.selection_model.min_time * self.selection_model.current_file.sample_rate
729
- )
730
- end_samp = int(
731
- self.selection_model.max_time * self.selection_model.current_file.sample_rate
732
- )
733
- if len(self.audio_plot.wave_form.y.shape) > 1:
734
- y = self.audio_plot.wave_form.y[
735
- begin_samp:end_samp, self.selection_model.selected_channel
736
- ]
737
- else:
738
- y = self.audio_plot.wave_form.y[begin_samp:end_samp]
739
- self.spectrogram_worker.stop()
740
- self.spectrogram_worker.set_params(
741
- y,
742
- self.selection_model.current_file.sound_file.sample_rate,
743
- self.selection_model.min_time,
744
- self.selection_model.max_time,
745
- self.selection_model.selected_channel,
746
- )
747
- self.spectrogram_worker.start()
748
- if self.selection_model.max_time - self.selection_model.min_time <= 10:
749
- self.pitch_track_worker.stop()
750
- self.pitch_track_worker.set_params(
751
- y,
752
- self.selection_model.current_file.sound_file.sample_rate,
753
- self.selection_model.min_time,
754
- self.selection_model.max_time,
755
- self.selection_model.selected_channel,
756
- self.audio_plot.pitch_track.bottom_point,
757
- self.audio_plot.pitch_track.top_point,
758
- )
759
- self.pitch_track_worker.start()
760
- self.auto_waveform_worker.stop()
761
- self.auto_waveform_worker.set_params(
762
- y,
763
- self.audio_plot.wave_form.bottom_point,
764
- self.audio_plot.wave_form.top_point,
765
- self.selection_model.min_time,
766
- self.selection_model.max_time,
767
- self.selection_model.selected_channel,
768
- )
769
- self.auto_waveform_worker.start()
770
- self.audio_plot_item.setRange(
771
- xRange=[self.selection_model.plot_min, self.selection_model.plot_max]
772
- )
773
- self.audio_plot.update_plot()
774
-
775
702
  def reset_plot(self, *args):
776
703
  self.reset_text_grid()
777
704
  self.audio_plot.wave_form.clear()
@@ -781,9 +708,8 @@ class UtteranceView(QtWidgets.QWidget):
781
708
  def update_plot(self, *args):
782
709
  if self.corpus_model.rowCount() == 0:
783
710
  return
784
- if self.selection_model.current_file is None or self.selection_model.min_time is None:
711
+ if self.file_model.file is None or self.selection_model.min_time is None:
785
712
  return
786
- self.get_latest_waveform()
787
713
  self.audio_plot.update_plot()
788
714
  self.draw_text_grid()
789
715
 
@@ -798,7 +724,7 @@ class UtteranceView(QtWidgets.QWidget):
798
724
  if tier.top_point > pos > tier.bottom_point:
799
725
  new_speaker_id = tier.speaker_id
800
726
  if new_speaker_id is not None and new_speaker_id != old_speaker_id:
801
- self.corpus_model.update_utterance_speaker(utterance, new_speaker_id)
727
+ self.file_model.update_utterance_speaker(utterance, new_speaker_id)
802
728
 
803
729
 
804
730
  class UtteranceLine(pg.InfiniteLine):
@@ -1041,20 +967,23 @@ class UtterancePGTextItem(pg.TextItem):
1041
967
  self.selection_model.viewChanged.connect(self.update_times)
1042
968
 
1043
969
  def update_times(self, begin, end):
1044
- self.hide()
1045
970
  self.view_min = begin
1046
971
  self.view_max = end
1047
- br = self.boundingRect()
972
+ if self.end <= self.view_min or self.begin >= self.view_max:
973
+ return
974
+ self.hide()
1048
975
  if (
1049
976
  self.view_min <= self.begin < self.view_max
1050
977
  or self.view_max >= self.end > self.view_min
1051
978
  or (self.begin <= self.view_min and self.end >= self.view_max)
1052
- ) and br.width() / self._cached_pixel_size[0] > 100:
979
+ ):
1053
980
  self.show()
1054
981
 
1055
982
  def boundingRect(self):
1056
983
  br = QtCore.QRectF(self.viewRect()) # bounds of containing ViewBox mapped to local coords.
1057
984
  vb = self.getViewBox()
985
+ if self.begin is None or self.view_min is None:
986
+ return br
1058
987
  visible_begin = max(self.begin, self.view_min)
1059
988
  visible_end = min(self.end, self.view_max)
1060
989
 
@@ -1532,24 +1461,24 @@ class Highlighter(QtGui.QSyntaxHighlighter):
1532
1461
 
1533
1462
 
1534
1463
  class MfaRegion(pg.LinearRegionItem):
1535
- dragFinished = QtCore.Signal(object)
1536
1464
  textEdited = QtCore.Signal(object, object)
1537
1465
  undoRequested = QtCore.Signal()
1538
1466
  redoRequested = QtCore.Signal()
1539
1467
  playRequested = QtCore.Signal()
1540
- selectRequested = QtCore.Signal(object, object, object, object)
1468
+ selectRequested = QtCore.Signal(object, object, object)
1541
1469
  audioSelected = QtCore.Signal(object, object)
1542
1470
  viewRequested = QtCore.Signal(object, object)
1543
1471
 
1544
1472
  settings = AnchorSettings()
1545
1473
 
1474
+ @profile
1546
1475
  def __init__(
1547
1476
  self,
1548
1477
  item: CtmInterval,
1549
1478
  corpus_model: CorpusModel,
1479
+ file_model: FileUtterancesModel,
1550
1480
  dictionary_model: typing.Optional[DictionaryTableModel],
1551
- selection_model: CorpusSelectionModel,
1552
- selected: bool = False,
1481
+ selection_model: FileSelectionModel,
1553
1482
  bottom_point: float = 0,
1554
1483
  top_point: float = 1,
1555
1484
  ):
@@ -1561,11 +1490,11 @@ class MfaRegion(pg.LinearRegionItem):
1561
1490
  if selection_model.settings.right_to_left:
1562
1491
  self.item_min, self.item_max = -self.item_max, -self.item_min
1563
1492
  self.corpus_model = corpus_model
1493
+ self.file_model = file_model
1564
1494
  self.dictionary_model = dictionary_model
1565
1495
  self.selection_model = selection_model
1566
1496
  self.bottom_point = bottom_point
1567
1497
  self.top_point = top_point
1568
- self.selected = selected
1569
1498
  self.span = (self.bottom_point, self.top_point)
1570
1499
  self.text_margin_pixels = 2
1571
1500
 
@@ -1584,7 +1513,7 @@ class MfaRegion(pg.LinearRegionItem):
1584
1513
  self.border_pen = pg.mkPen(self.break_line_color, width=2)
1585
1514
  self.border_pen.setCapStyle(QtCore.Qt.PenCapStyle.FlatCap)
1586
1515
 
1587
- if self.selected:
1516
+ if self.selection_model.checkSelected(getattr(self.item, "id", None)):
1588
1517
  self.background_brush = pg.mkBrush(self.selected_interval_color)
1589
1518
  else:
1590
1519
  # self.interval_background_color.setAlpha(0)
@@ -1604,44 +1533,6 @@ class MfaRegion(pg.LinearRegionItem):
1604
1533
  self._boundingRectCache = None
1605
1534
  self.setBrush(self.background_brush)
1606
1535
  self.movable = False
1607
-
1608
- # note LinearRegionItem.Horizontal and LinearRegionItem.Vertical
1609
- # are kept for backward compatibility.
1610
- lineKwds = dict(
1611
- movable=False,
1612
- bounds=None,
1613
- span=self.span,
1614
- pen=self.pen,
1615
- hoverPen=self.hoverPen,
1616
- movingPen=self.movingPen,
1617
- )
1618
- self.lines = [
1619
- UtteranceLine(
1620
- QtCore.QPointF(self.item_min, 0),
1621
- angle=90,
1622
- initial=True,
1623
- view_min=self.selection_model.plot_min,
1624
- view_max=self.selection_model.plot_max,
1625
- **lineKwds,
1626
- ),
1627
- UtteranceLine(
1628
- QtCore.QPointF(self.item_max, 0),
1629
- angle=90,
1630
- initial=False,
1631
- view_min=self.selection_model.plot_min,
1632
- view_max=self.selection_model.plot_max,
1633
- **lineKwds,
1634
- ),
1635
- ]
1636
-
1637
- for line in self.lines:
1638
- line.setZValue(30)
1639
- line.setParentItem(self)
1640
- line.sigPositionChangeFinished.connect(self.lineMoveFinished)
1641
- self.lines[0].sigPositionChanged.connect(self._line0Moved)
1642
- self.lines[1].sigPositionChanged.connect(self._line1Moved)
1643
- self.lines[0].hoverChanged.connect(self.popup)
1644
- self.lines[1].hoverChanged.connect(self.popup)
1645
1536
  self.cached_visible_duration = None
1646
1537
  self.cached_view = None
1647
1538
 
@@ -1650,33 +1541,6 @@ class MfaRegion(pg.LinearRegionItem):
1650
1541
  p.setPen(self.border_pen)
1651
1542
  p.drawRect(self.boundingRect())
1652
1543
 
1653
- def mouseDragEvent(self, ev):
1654
- if not self.movable or ev.button() != QtCore.Qt.MouseButton.LeftButton:
1655
- return
1656
- ev.accept()
1657
-
1658
- if ev.isStart():
1659
- bdp = ev.buttonDownPos()
1660
- self.cursorOffsets = [line.pos() - bdp for line in self.lines]
1661
- self.startPositions = [line.pos() for line in self.lines]
1662
- self.moving = True
1663
-
1664
- if not self.moving:
1665
- return
1666
-
1667
- # self.lines[0].blockSignals(True) # only want to update once
1668
- # for i, l in enumerate(self.lines):
1669
- # l.setPos(self.cursorOffsets[i] + ev.pos())
1670
- # self.lines[0].blockSignals(False)
1671
- self.prepareGeometryChange()
1672
-
1673
- if ev.isFinish():
1674
- self.moving = False
1675
- self.dragFinished.emit(ev.pos())
1676
- self.sigRegionChangeFinished.emit(self)
1677
- else:
1678
- self.sigRegionChanged.emit(self)
1679
-
1680
1544
  def mouseClickEvent(self, ev: QtGui.QMouseEvent):
1681
1545
  if ev.button() != QtCore.Qt.MouseButton.LeftButton:
1682
1546
  ev.ignore()
@@ -1693,26 +1557,14 @@ class MfaRegion(pg.LinearRegionItem):
1693
1557
  self.viewRequested.emit(self.item_min - padding, self.item_max + padding)
1694
1558
  ev.accept()
1695
1559
 
1696
- def change_editing(self, editable: bool):
1697
- self.movable = editable
1698
- self.lines[0].movable = editable
1699
- self.lines[1].movable = editable
1700
-
1701
1560
  def setSelected(self, selected: bool):
1702
- self.selected = selected
1703
- if self.selected:
1561
+ if selected:
1704
1562
  self.setBrush(pg.mkBrush(self.selected_interval_color))
1705
1563
  else:
1706
1564
  # self.interval_background_color.setAlpha(0)
1707
1565
  self.setBrush(pg.mkBrush(self.interval_background_color))
1708
1566
  self.update()
1709
1567
 
1710
- def popup(self, hover: bool):
1711
- if hover or self.moving or self.lines[0].moving or self.lines[1].moving:
1712
- self.setZValue(30)
1713
- else:
1714
- self.setZValue(0)
1715
-
1716
1568
  def setMouseHover(self, hover: bool):
1717
1569
  # Inform the item that the mouse is(not) hovering over it
1718
1570
  if self.mouseHovering == hover:
@@ -1721,30 +1573,38 @@ class MfaRegion(pg.LinearRegionItem):
1721
1573
  self.popup(hover)
1722
1574
  self.update()
1723
1575
 
1724
- def select_self(self, deselect=False, reset=True, focus=False):
1576
+ def select_self(self, deselect=False, reset=True):
1725
1577
  self.selected = True
1726
1578
  if self.selected and not deselect and not reset:
1727
1579
  return
1728
1580
 
1729
1581
 
1730
1582
  class AlignmentRegion(MfaRegion):
1583
+ @profile
1731
1584
  def __init__(
1732
1585
  self,
1733
1586
  phone_interval: CtmInterval,
1734
1587
  corpus_model: CorpusModel,
1588
+ file_model: FileUtterancesModel,
1735
1589
  selection_model: CorpusSelectionModel,
1736
- selected: bool = False,
1737
1590
  bottom_point: float = 0,
1738
1591
  top_point: float = 1,
1739
1592
  ):
1740
1593
  super().__init__(
1741
- phone_interval, corpus_model, None, selection_model, selected, bottom_point, top_point
1594
+ phone_interval,
1595
+ corpus_model,
1596
+ file_model,
1597
+ None,
1598
+ selection_model,
1599
+ bottom_point,
1600
+ top_point,
1742
1601
  )
1743
1602
  self.original_text = self.item.label
1744
1603
 
1745
1604
  self.text = pg.TextItem(
1746
1605
  self.item.label, anchor=(0.5, 0.5), color=self.text_color # , border=pg.mkColor("r")
1747
1606
  )
1607
+ self.text.setVisible(False)
1748
1608
 
1749
1609
  self.text.setFont(self.settings.font)
1750
1610
  options = QtGui.QTextOption()
@@ -1753,22 +1613,30 @@ class AlignmentRegion(MfaRegion):
1753
1613
  self.text.setParentItem(self)
1754
1614
  self.per_tier_range = self.top_point - self.bottom_point
1755
1615
 
1616
+ def viewRangeChanged(self):
1617
+ if (self.item_max - self.item_min) / (
1618
+ self.selection_model.max_time - self.selection_model.min_time
1619
+ ) < 0.001:
1620
+ self.hide()
1621
+ else:
1622
+ self.show()
1623
+ super().viewRangeChanged()
1624
+
1756
1625
  def boundingRect(self):
1757
1626
  br = QtCore.QRectF(self.viewRect()) # bounds of containing ViewBox mapped to local coords.
1758
1627
  vb = self.getViewBox()
1759
1628
 
1760
1629
  pixel_size = vb.viewPixelSize()
1761
- rng = self.getRegion()
1762
1630
 
1763
- br.setLeft(rng[0])
1764
- br.setRight(rng[1])
1631
+ br.setLeft(self.item_min)
1632
+ br.setRight(self.item_max)
1765
1633
 
1766
1634
  br.setTop(self.top_point)
1767
1635
  # br.setBottom(self.top_point-self.per_tier_range)
1768
1636
  br.setBottom(self.bottom_point + 0.01)
1769
1637
  try:
1770
- visible_begin = max(rng[0], self.selection_model.plot_min)
1771
- visible_end = min(rng[1], self.selection_model.plot_max)
1638
+ visible_begin = max(self.item_min, self.selection_model.plot_min)
1639
+ visible_end = min(self.item_max, self.selection_model.plot_max)
1772
1640
  except TypeError:
1773
1641
  return br
1774
1642
  visible_duration = visible_end - visible_begin
@@ -1794,39 +1662,47 @@ class PhoneRegion(AlignmentRegion):
1794
1662
  self,
1795
1663
  phone_interval: CtmInterval,
1796
1664
  corpus_model: CorpusModel,
1665
+ file_model: FileUtterancesModel,
1797
1666
  selection_model: CorpusSelectionModel,
1798
- selected: bool = False,
1799
1667
  bottom_point: float = 0,
1800
1668
  top_point: float = 1,
1801
1669
  ):
1802
1670
  super().__init__(
1803
- phone_interval, corpus_model, selection_model, selected, bottom_point, top_point
1671
+ phone_interval, corpus_model, file_model, selection_model, bottom_point, top_point
1804
1672
  )
1805
1673
 
1806
1674
 
1807
1675
  class WordRegion(AlignmentRegion):
1676
+ highlightRequested = QtCore.Signal(object)
1677
+
1808
1678
  def __init__(
1809
1679
  self,
1810
- phone_interval: CtmInterval,
1680
+ word_interval: CtmInterval,
1811
1681
  corpus_model: CorpusModel,
1682
+ file_model: FileUtterancesModel,
1812
1683
  selection_model: CorpusSelectionModel,
1813
- selected: bool = False,
1814
1684
  bottom_point: float = 0,
1815
1685
  top_point: float = 1,
1816
1686
  ):
1817
1687
  super().__init__(
1818
- phone_interval, corpus_model, selection_model, selected, bottom_point, top_point
1688
+ word_interval, corpus_model, file_model, selection_model, bottom_point, top_point
1819
1689
  )
1820
1690
 
1691
+ def mouseClickEvent(self, ev: QtGui.QMouseEvent):
1692
+ search_term = TextFilterQuery(self.item.label, word=True)
1693
+ self.highlightRequested.emit(search_term)
1694
+ super().mouseClickEvent(ev)
1695
+
1821
1696
 
1822
1697
  class UtteranceRegion(MfaRegion):
1698
+ @profile
1823
1699
  def __init__(
1824
1700
  self,
1825
1701
  utterance: workers.UtteranceData,
1826
1702
  corpus_model: CorpusModel,
1703
+ file_model: FileUtterancesModel,
1827
1704
  dictionary_model: DictionaryTableModel,
1828
- selection_model: CorpusSelectionModel,
1829
- selected: bool = False,
1705
+ selection_model: FileSelectionModel,
1830
1706
  bottom_point: float = 0,
1831
1707
  top_point: float = 1,
1832
1708
  extra_tiers=None,
@@ -1836,9 +1712,9 @@ class UtteranceRegion(MfaRegion):
1836
1712
  super().__init__(
1837
1713
  utterance,
1838
1714
  corpus_model,
1715
+ file_model,
1839
1716
  dictionary_model,
1840
1717
  selection_model,
1841
- selected,
1842
1718
  bottom_point,
1843
1719
  top_point,
1844
1720
  )
@@ -1852,8 +1728,45 @@ class UtteranceRegion(MfaRegion):
1852
1728
  visible_tiers = self.settings.visible_tiers
1853
1729
  self.num_tiers = len([x for x in extra_tiers if visible_tiers[x]]) + 1
1854
1730
  self.per_tier_range = (top_point - bottom_point) / self.num_tiers
1731
+ self.selected = self.selection_model.checkSelected(self.item.id)
1855
1732
 
1856
- self.setMovable(True)
1733
+ # note LinearRegionItem.Horizontal and LinearRegionItem.Vertical
1734
+ # are kept for backward compatibility.
1735
+ lineKwds = dict(
1736
+ movable=True,
1737
+ bounds=None,
1738
+ span=self.span,
1739
+ pen=self.pen,
1740
+ hoverPen=self.hoverPen,
1741
+ movingPen=self.movingPen,
1742
+ )
1743
+ self.lines = [
1744
+ UtteranceLine(
1745
+ QtCore.QPointF(self.item_min, 0),
1746
+ angle=90,
1747
+ initial=True,
1748
+ view_min=self.selection_model.plot_min,
1749
+ view_max=self.selection_model.plot_max,
1750
+ **lineKwds,
1751
+ ),
1752
+ UtteranceLine(
1753
+ QtCore.QPointF(self.item_max, 0),
1754
+ angle=90,
1755
+ initial=False,
1756
+ view_min=self.selection_model.plot_min,
1757
+ view_max=self.selection_model.plot_max,
1758
+ **lineKwds,
1759
+ ),
1760
+ ]
1761
+
1762
+ for line in self.lines:
1763
+ line.setZValue(30)
1764
+ line.setParentItem(self)
1765
+ line.sigPositionChangeFinished.connect(self.lineMoveFinished)
1766
+ self.lines[0].sigPositionChanged.connect(self._line0Moved)
1767
+ self.lines[1].sigPositionChanged.connect(self._line1Moved)
1768
+ self.lines[0].hoverChanged.connect(self.popup)
1769
+ self.lines[1].hoverChanged.connect(self.popup)
1857
1770
 
1858
1771
  self.corpus_model.utteranceTextUpdated.connect(self.update_text_from_model)
1859
1772
  self.original_text = self.item.text
@@ -1945,6 +1858,8 @@ class UtteranceRegion(MfaRegion):
1945
1858
  if intervals is None:
1946
1859
  continue
1947
1860
  for interval in intervals:
1861
+ # if (interval.end - interval.begin) /(self.selection_model.max_time -self.selection_model.min_time) < 0.01:
1862
+ # continue
1948
1863
  if lookup == "transcription_text":
1949
1864
  interval_reg = TranscriberTextRegion(
1950
1865
  self,
@@ -1964,8 +1879,8 @@ class UtteranceRegion(MfaRegion):
1964
1879
  interval_reg = PhoneRegion(
1965
1880
  interval,
1966
1881
  self.corpus_model,
1882
+ self.file_model,
1967
1883
  selection_model=selection_model,
1968
- selected=False,
1969
1884
  top_point=tier_top_point,
1970
1885
  bottom_point=tier_bottom_point,
1971
1886
  )
@@ -1974,12 +1889,13 @@ class UtteranceRegion(MfaRegion):
1974
1889
  interval_reg = WordRegion(
1975
1890
  interval,
1976
1891
  self.corpus_model,
1892
+ self.file_model,
1977
1893
  selection_model=selection_model,
1978
- selected=False,
1979
1894
  top_point=tier_top_point,
1980
1895
  bottom_point=tier_bottom_point,
1981
1896
  )
1982
1897
  interval_reg.setParentItem(self)
1898
+ interval_reg.highlightRequested.connect(self.highlighter.setSearchTerm)
1983
1899
 
1984
1900
  else:
1985
1901
  interval_reg = IntervalTextRegion(
@@ -2001,6 +1917,25 @@ class UtteranceRegion(MfaRegion):
2001
1917
  self.show()
2002
1918
  self.available_speakers = available_speakers
2003
1919
 
1920
+ def change_editing(self, editable: bool):
1921
+ self.lines[0].movable = editable
1922
+ self.lines[1].movable = editable
1923
+ self.text_edit.setReadOnly(not editable)
1924
+
1925
+ def popup(self, hover: bool):
1926
+ if hover or self.moving or self.lines[0].moving or self.lines[1].moving:
1927
+ self.setZValue(30)
1928
+ else:
1929
+ self.setZValue(0)
1930
+
1931
+ def setMovable(self, m=True):
1932
+ """Set lines to be movable by the user, or not. If lines are movable, they will
1933
+ also accept HoverEvents."""
1934
+ for line in self.lines:
1935
+ line.setMovable(m)
1936
+ self.movable = False
1937
+ self.setAcceptHoverEvents(False)
1938
+
2004
1939
  def contextMenuEvent(self, ev: QtWidgets.QGraphicsSceneContextMenuEvent):
2005
1940
  menu = QtWidgets.QMenu()
2006
1941
  change_speaker_menu = QtWidgets.QMenu("Change speaker")
@@ -2032,6 +1967,17 @@ class UtteranceRegion(MfaRegion):
2032
1967
  a.toggled.connect(self.update_tier_visibility)
2033
1968
  visible_tiers_menu.addAction(a)
2034
1969
  menu.addMenu(visible_tiers_menu)
1970
+ menu.addSeparator()
1971
+
1972
+ a = QtGui.QAction(menu)
1973
+ a.setText("Split utterance")
1974
+ a.triggered.connect(self.split_utterance)
1975
+ menu.addAction(a)
1976
+
1977
+ a = QtGui.QAction(menu)
1978
+ a.setText("Delete utterance")
1979
+ a.triggered.connect(self.delete_utterance)
1980
+ menu.addAction(a)
2035
1981
  change_speaker_menu.setStyleSheet(self.settings.menu_style_sheet)
2036
1982
  visible_tiers_menu.setStyleSheet(self.settings.menu_style_sheet)
2037
1983
  menu.setStyleSheet(self.settings.menu_style_sheet)
@@ -2050,7 +1996,7 @@ class UtteranceRegion(MfaRegion):
2050
1996
  if dialog.exec_():
2051
1997
  speaker_id = dialog.speaker_dropdown.current_text()
2052
1998
  if isinstance(speaker_id, int):
2053
- self.corpus_model.update_utterance_speaker(self.item, speaker_id)
1999
+ self.file_model.update_utterance_speaker(self.item, speaker_id)
2054
2000
 
2055
2001
  def update_speaker(self):
2056
2002
  speaker_name = self.sender().text()
@@ -2058,21 +2004,21 @@ class UtteranceRegion(MfaRegion):
2058
2004
  speaker_id = 0
2059
2005
  else:
2060
2006
  speaker_id = self.available_speakers[speaker_name]
2061
- self.corpus_model.update_utterance_speaker(self.item, speaker_id)
2007
+ self.file_model.update_utterance_speaker(self.item, speaker_id)
2008
+
2009
+ def split_utterance(self):
2010
+ self.file_model.split_utterances([self.item])
2011
+
2012
+ def delete_utterance(self):
2013
+ self.file_model.delete_utterances([self.item])
2062
2014
 
2063
2015
  def refresh_timer(self):
2064
2016
  self.timer.start(500)
2065
2017
  self.update()
2066
2018
 
2067
- def change_editing(self, editable: bool):
2068
- super().change_editing(editable)
2069
- self.text_edit.setReadOnly(not editable)
2070
-
2071
- def select_self(self, deselect=False, reset=True, focus=False):
2072
- self.selected = True
2073
- if self.selected and not deselect and not reset:
2074
- return
2075
- self.selectRequested.emit(self.item.id, deselect, reset, focus)
2019
+ def select_self(self, deselect=False, reset=True):
2020
+ self.setSelected(not deselect)
2021
+ self.selectRequested.emit(self.item.id, deselect, reset)
2076
2022
 
2077
2023
  def mouseDoubleClickEvent(self, ev: QtGui.QMouseEvent):
2078
2024
  if ev.button() != QtCore.Qt.MouseButton.LeftButton:
@@ -2080,7 +2026,10 @@ class UtteranceRegion(MfaRegion):
2080
2026
  return
2081
2027
  deselect = False
2082
2028
  reset = True
2083
- if ev.modifiers() == QtCore.Qt.Modifier.CTRL:
2029
+ if ev.modifiers() in [
2030
+ QtCore.Qt.KeyboardModifier.ControlModifier,
2031
+ QtCore.Qt.KeyboardModifier.ShiftModifier,
2032
+ ]:
2084
2033
  reset = False
2085
2034
  if self.selected:
2086
2035
  deselect = True
@@ -2089,7 +2038,7 @@ class UtteranceRegion(MfaRegion):
2089
2038
  self.selected = True
2090
2039
  else:
2091
2040
  self.selected = True
2092
- self.select_self(deselect=deselect, reset=reset, focus=True)
2041
+ self.select_self(deselect=deselect, reset=reset)
2093
2042
  ev.accept()
2094
2043
 
2095
2044
  def mouseClickEvent(self, ev: QtGui.QMouseEvent):
@@ -2098,7 +2047,10 @@ class UtteranceRegion(MfaRegion):
2098
2047
  return
2099
2048
  deselect = False
2100
2049
  reset = True
2101
- if ev.modifiers() == QtCore.Qt.Modifier.CTRL:
2050
+ if ev.modifiers() in [
2051
+ ev.modifiers().ControlModifier,
2052
+ ev.modifiers().ShiftModifier,
2053
+ ]:
2102
2054
  reset = False
2103
2055
  if self.selected:
2104
2056
  deselect = True
@@ -2107,7 +2059,7 @@ class UtteranceRegion(MfaRegion):
2107
2059
  self.selected = True
2108
2060
  else:
2109
2061
  self.selected = True
2110
- self.select_self(deselect=deselect, reset=reset, focus=False)
2062
+ self.select_self(deselect=deselect, reset=reset)
2111
2063
  ev.accept()
2112
2064
 
2113
2065
  def update_view_times(self):
@@ -2349,7 +2301,7 @@ class AudioPlots(pg.GraphicsObject):
2349
2301
  def __init__(self, top_point, separator_point, bottom_point):
2350
2302
  super().__init__()
2351
2303
  self.settings = AnchorSettings()
2352
- self.selection_model: typing.Optional[CorpusSelectionModel] = None
2304
+ self.selection_model: typing.Optional[FileSelectionModel] = None
2353
2305
  self.top_point = top_point
2354
2306
  self.separator_point = separator_point
2355
2307
  self.bottom_point = bottom_point
@@ -2434,7 +2386,45 @@ class AudioPlots(pg.GraphicsObject):
2434
2386
  if ev.button() != QtCore.Qt.MouseButton.LeftButton:
2435
2387
  ev.ignore()
2436
2388
  return
2437
- self.selection_model.request_start_time(ev.pos().x())
2389
+ if ev.modifiers() in [
2390
+ QtCore.Qt.KeyboardModifier.ControlModifier,
2391
+ QtCore.Qt.KeyboardModifier.ShiftModifier,
2392
+ ]:
2393
+ time = ev.pos().x()
2394
+ if self.selection_model.selected_max_time is not None:
2395
+ if (
2396
+ self.selection_model.selected_min_time
2397
+ < time
2398
+ < self.selection_model.selected_max_time
2399
+ ):
2400
+ if (
2401
+ time - self.selection_model.selected_min_time
2402
+ < self.selection_model.selected_max_time - time
2403
+ ):
2404
+ min_time = time
2405
+ max_time = self.selection_model.selected_max_time
2406
+ else:
2407
+ min_time = self.selection_model.selected_min_time
2408
+ max_time = time
2409
+ else:
2410
+ min_time = min(
2411
+ time,
2412
+ self.selection_model.selected_min_time,
2413
+ self.selection_model.selected_max_time,
2414
+ )
2415
+ max_time = max(
2416
+ time,
2417
+ self.selection_model.selected_min_time,
2418
+ self.selection_model.selected_max_time,
2419
+ )
2420
+ else:
2421
+ min_time = min(time, self.selection_model.selected_min_time)
2422
+ max_time = max(time, self.selection_model.selected_min_time)
2423
+ self.selection_area.setRegion((min_time, max_time))
2424
+ self.selection_area.setVisible(True)
2425
+ self.selection_model.select_audio(min_time, max_time)
2426
+ else:
2427
+ self.selection_model.request_start_time(ev.pos().x())
2438
2428
  ev.accept()
2439
2429
 
2440
2430
  def hoverEvent(self, ev):
@@ -2476,9 +2466,9 @@ class AudioPlots(pg.GraphicsObject):
2476
2466
 
2477
2467
  def update_plot(self):
2478
2468
  if (
2479
- self.selection_model.current_file is None
2480
- or self.selection_model.current_file.sound_file is None
2481
- or not os.path.exists(self.selection_model.current_file.sound_file.sound_file_path)
2469
+ self.selection_model.model().file is None
2470
+ or self.selection_model.model().file.sound_file is None
2471
+ or not os.path.exists(self.selection_model.model().file.sound_file.sound_file_path)
2482
2472
  ):
2483
2473
  return
2484
2474
  self.rect.setLeft(self.selection_model.plot_min)
@@ -2490,48 +2480,68 @@ class AudioPlots(pg.GraphicsObject):
2490
2480
 
2491
2481
 
2492
2482
  class SpeakerTier(pg.GraphicsObject):
2493
- dragFinished = QtCore.Signal(object, object)
2494
2483
  receivedWheelEvent = QtCore.Signal(object)
2495
2484
  draggingLine = QtCore.Signal(object)
2496
2485
  lineDragFinished = QtCore.Signal(object)
2497
2486
 
2498
2487
  def __init__(
2499
- self, bottom_point, top_point, speaker_id: int, speaker_name: str, search_term=None
2488
+ self,
2489
+ top_point,
2490
+ bottom_point,
2491
+ speaker_id: int,
2492
+ speaker_name: str,
2493
+ corpus_model: CorpusModel,
2494
+ file_model: FileUtterancesModel,
2495
+ selection_model: FileSelectionModel,
2496
+ dictionary_model: DictionaryTableModel,
2497
+ search_term: str = None,
2500
2498
  ):
2501
2499
  super().__init__()
2500
+ self.file_model = file_model
2501
+ self.corpus_model = corpus_model
2502
+ self.selection_model = selection_model
2503
+ self.dictionary_model = dictionary_model
2502
2504
  self.settings = AnchorSettings()
2503
- self.corpus_model: Optional[CorpusModel] = None
2504
- self.selection_model: Optional[CorpusSelectionModel] = None
2505
2505
  self.search_term = search_term
2506
2506
  self.speaker_id = speaker_id
2507
2507
  self.speaker_name = speaker_name
2508
2508
  self.speaker_index = 0
2509
- self.textgrid_top_point = top_point
2510
2509
  self.top_point = top_point
2511
2510
  self.speaker_label = pg.TextItem(self.speaker_name, color=self.settings.accent_base_color)
2512
2511
  self.speaker_label.setFont(self.settings.font)
2513
2512
  self.speaker_label.setParentItem(self)
2514
2513
  self.speaker_label.setZValue(40)
2515
2514
  self.bottom_point = bottom_point
2516
- self.textgrid_bottom_point = bottom_point
2517
2515
  self.annotation_range = self.top_point - self.bottom_point
2518
2516
  self.extra_tiers = {}
2519
- self.utterances = []
2520
2517
  self.visible_utterances: dict[str, UtteranceRegion] = {}
2521
2518
  self.background_brush = pg.mkBrush(self.settings.primary_very_dark_color)
2522
2519
  self.border = pg.mkPen(self.settings.accent_light_color)
2523
2520
  self.picture = QtGui.QPicture()
2521
+ self.has_visible_utterances = False
2522
+ self.has_selected_utterances = False
2523
+ self.rect = QtCore.QRectF(
2524
+ left=self.selection_model.plot_min,
2525
+ riht=self.selection_model.plot_max,
2526
+ top=self.top_point,
2527
+ bottom=self.bottom_point,
2528
+ )
2529
+ self._generate_picture()
2530
+ self.corpus_model.lockCorpus.connect(self.lock)
2531
+ self.corpus_model.refreshUtteranceText.connect(self.refreshTexts)
2532
+ self.selection_model.selectionChanged.connect(self.update_select)
2533
+ self.selection_model.model().utterancesReady.connect(self.refresh)
2524
2534
 
2525
2535
  def wheelEvent(self, ev):
2526
2536
  self.receivedWheelEvent.emit(ev)
2527
2537
 
2528
- def mouseDoubleClickEvent(self, ev):
2529
- if ev.button() != QtCore.Qt.MouseButton.LeftButton:
2538
+ def mouseClickEvent(self, ev):
2539
+ if ev.button() != QtCore.Qt.MouseButton.RightButton:
2530
2540
  ev.ignore()
2531
2541
  return
2532
2542
  x = ev.pos().x()
2533
2543
  begin = max(x - 0.5, 0)
2534
- end = min(x + 0.5, self.selection_model.current_file.duration)
2544
+ end = min(x + 0.5, self.selection_model.model().file.duration)
2535
2545
  for x in self.visible_utterances.values():
2536
2546
  if begin >= x.item_min and end <= x.item_max:
2537
2547
  ev.accept()
@@ -2542,10 +2552,40 @@ class SpeakerTier(pg.GraphicsObject):
2542
2552
  end = x.item_min
2543
2553
  break
2544
2554
  if end - begin > 0.001:
2545
- self.corpus_model.create_utterance(
2546
- self.selection_model.current_file, self.speaker_id, begin, end
2547
- )
2548
- ev.accept()
2555
+ menu = QtWidgets.QMenu()
2556
+
2557
+ a = QtGui.QAction(menu)
2558
+ a.setText("Create utterance")
2559
+ a.triggered.connect(functools.partial(self.create_utterance, begin=begin, end=end))
2560
+ menu.addAction(a)
2561
+ menu.setStyleSheet(self.settings.menu_style_sheet)
2562
+ menu.exec_(ev.screenPos())
2563
+
2564
+ def contextMenuEvent(self, ev):
2565
+ x = ev.pos().x()
2566
+ begin = max(x - 0.5, 0)
2567
+ end = min(x + 0.5, self.selection_model.model().file.duration)
2568
+ for x in self.visible_utterances.values():
2569
+ if begin >= x.item_min and end <= x.item_max:
2570
+ ev.accept()
2571
+ return
2572
+ if begin < x.item_max and begin > x.item_max:
2573
+ begin = x.item_max
2574
+ if end > x.item_min and end < x.item_min:
2575
+ end = x.item_min
2576
+ break
2577
+ if end - begin > 0.001:
2578
+ menu = QtWidgets.QMenu()
2579
+
2580
+ a = QtGui.QAction(menu)
2581
+ a.setText("Create utterance")
2582
+ a.triggered.connect(functools.partial(self.create_utterance, begin=begin, end=end))
2583
+ menu.addAction(a)
2584
+ menu.setStyleSheet(self.settings.menu_style_sheet)
2585
+ menu.exec_(ev.screenPos())
2586
+
2587
+ def create_utterance(self, begin, end):
2588
+ self.file_model.create_utterance(self.speaker_id, begin, end)
2549
2589
 
2550
2590
  def setSearchterm(self, term):
2551
2591
  self.search_term = term
@@ -2558,22 +2598,7 @@ class SpeakerTier(pg.GraphicsObject):
2558
2598
  def paint(self, p, *args):
2559
2599
  p.drawPicture(0, 0, self.picture)
2560
2600
 
2561
- def set_speaker_index(self, index, num_speakers):
2562
- self.speaker_index = index
2563
- speaker_tier_range = self.annotation_range / num_speakers
2564
- self.top_point = self.textgrid_top_point - (speaker_tier_range * self.speaker_index)
2565
- self.bottom_point = self.top_point - speaker_tier_range
2566
- self.rect = QtCore.QRectF(
2567
- left=self.selection_model.plot_min,
2568
- top=self.top_point,
2569
- width=self.selection_model.plot_max - self.selection_model.plot_min,
2570
- height=speaker_tier_range,
2571
- )
2572
- self.rect.setHeight(speaker_tier_range)
2573
- self._generate_picture()
2574
-
2575
2601
  def _generate_picture(self):
2576
- self.speaker_label.setPos(self.selection_model.plot_min, self.top_point)
2577
2602
  self.picture = QtGui.QPicture()
2578
2603
  painter = QtGui.QPainter(self.picture)
2579
2604
  painter.setPen(self.border)
@@ -2588,22 +2613,6 @@ class SpeakerTier(pg.GraphicsObject):
2588
2613
  def set_available_speakers(self, available_speakers):
2589
2614
  self.available_speakers = available_speakers
2590
2615
 
2591
- def set_models(
2592
- self,
2593
- corpus_model: CorpusModel,
2594
- selection_model: CorpusSelectionModel,
2595
- dictionary_model: DictionaryTableModel,
2596
- ):
2597
- self.corpus_model = corpus_model
2598
- self.selection_model = selection_model
2599
- self.dictionary_model = dictionary_model
2600
- for reg in self.visible_utterances.values():
2601
- reg.highlighter.set_models(self.dictionary_model)
2602
- # self.corpus_model.changeCommandFired.connect(self.refresh)
2603
- self.corpus_model.lockCorpus.connect(self.lock)
2604
- self.corpus_model.refreshUtteranceText.connect(self.refreshTexts)
2605
- self.selection_model.selectionChanged.connect(self.update_select)
2606
-
2607
2616
  def lock(self):
2608
2617
  for utt in self.visible_utterances.values():
2609
2618
  utt.setMovable(False)
@@ -2629,36 +2638,55 @@ class SpeakerTier(pg.GraphicsObject):
2629
2638
  if reg.scene() is not None:
2630
2639
  reg.scene().removeItem(reg)
2631
2640
  self.visible_utterances = {}
2632
- self.other_intervals = []
2633
2641
 
2642
+ @profile
2634
2643
  def refresh(self, *args):
2644
+ self.hide()
2635
2645
  if self.selection_model.plot_min is None:
2636
2646
  return
2637
- self.rect.setLeft(self.selection_model.plot_min)
2638
- self.rect.setRight(self.selection_model.plot_max)
2639
- self._generate_picture()
2647
+ # self.rect.setLeft(self.selection_model.plot_min)
2648
+ # self.rect.setRight(self.selection_model.plot_max)
2649
+ # self._generate_picture()
2640
2650
  self.has_visible_utterances = False
2641
- for u in self.utterances:
2642
- if u.end < self.selection_model.min_time:
2643
- if u.id in self.visible_utterances:
2644
- self.visible_utterances[u.id].hide()
2645
- continue
2646
- if u.begin > self.selection_model.max_time:
2647
- if u.id in self.visible_utterances:
2648
- self.visible_utterances[u.id].hide()
2651
+ self.has_selected_utterances = False
2652
+ self.speaker_label.setPos(self.selection_model.plot_min, self.top_point)
2653
+ cleanup_ids = []
2654
+ model_visible_utterances = self.selection_model.visible_utterances()
2655
+ visible_ids = [x.id for x in model_visible_utterances]
2656
+ for reg in self.visible_utterances.values():
2657
+ reg.hide()
2658
+ if (
2659
+ self.selection_model.min_time - reg.item.end > 15
2660
+ or reg.item.begin - self.selection_model.max_time > 15
2661
+ or (
2662
+ reg.item.id not in visible_ids
2663
+ and (
2664
+ reg.item.begin < self.selection_model.max_time
2665
+ or reg.item.end > self.selection_model.min_time
2666
+ )
2667
+ )
2668
+ ):
2669
+ if reg.scene() is not None:
2670
+ reg.scene().removeItem(reg)
2671
+ cleanup_ids.append(reg.item.id)
2672
+ self.visible_utterances = {
2673
+ k: v for k, v in self.visible_utterances.items() if k not in cleanup_ids
2674
+ }
2675
+ for u in model_visible_utterances:
2676
+ if u.speaker_id != self.speaker_id:
2649
2677
  continue
2650
- self.has_visible_utterances = True
2651
2678
  if u.id in self.visible_utterances:
2679
+ self.visible_utterances[u.id].setSelected(self.selection_model.checkSelected(u.id))
2652
2680
  self.visible_utterances[u.id].show()
2653
2681
  continue
2654
- selected = self.selection_model.checkSelected(u)
2682
+ self.has_visible_utterances = True
2655
2683
  # Utterance region always at the top
2656
2684
  reg = UtteranceRegion(
2657
2685
  u,
2658
2686
  self.corpus_model,
2687
+ self.file_model,
2659
2688
  self.dictionary_model,
2660
2689
  selection_model=self.selection_model,
2661
- selected=selected,
2662
2690
  extra_tiers=self.extra_tiers,
2663
2691
  available_speakers=self.available_speakers,
2664
2692
  bottom_point=self.bottom_point,
@@ -2667,7 +2695,6 @@ class SpeakerTier(pg.GraphicsObject):
2667
2695
  )
2668
2696
  reg.sigRegionChanged.connect(self.check_utterance_bounds)
2669
2697
  reg.sigRegionChangeFinished.connect(self.update_utterance)
2670
- reg.dragFinished.connect(self.update_selected_speaker)
2671
2698
  reg.lines[0].sigPositionChanged.connect(self.draggingLine.emit)
2672
2699
  reg.lines[0].sigPositionChangeFinished.connect(self.lineDragFinished.emit)
2673
2700
  reg.lines[1].sigPositionChanged.connect(self.draggingLine.emit)
@@ -2683,17 +2710,13 @@ class SpeakerTier(pg.GraphicsObject):
2683
2710
  reg.setParentItem(self)
2684
2711
  self.visible_utterances[u.id] = reg
2685
2712
 
2686
- def update_utterance_text(self, utterance, new_text):
2687
- self.corpus_model.update_utterance_text(utterance, text=new_text)
2713
+ self.show()
2688
2714
 
2689
- def update_selected_speaker(self, pos):
2690
- pos = pos.y()
2691
- reg = self.sender()
2692
- utterance = reg.item
2693
- self.dragFinished.emit(utterance, pos)
2715
+ def update_utterance_text(self, utterance, new_text):
2716
+ self.selection_model.model().update_utterance_text(utterance, text=new_text)
2694
2717
 
2695
2718
  def update_select(self):
2696
- selected_rows = {x.id for x in self.selection_model.selectedUtterances()}
2719
+ selected_rows = {x.id for x in self.selection_model.selected_utterances()}
2697
2720
  for r in self.visible_utterances.values():
2698
2721
  if r.item.id in selected_rows:
2699
2722
  r.setSelected(True)
@@ -2708,15 +2731,21 @@ class SpeakerTier(pg.GraphicsObject):
2708
2731
  if end > 0:
2709
2732
  reg.setRegion([beg, 0])
2710
2733
  return
2711
- if -end > self.selection_model.current_file.duration:
2712
- reg.setRegion([beg, self.selection_model.current_file.duration])
2734
+ if (
2735
+ self.selection_model.model().file is not None
2736
+ and -end > self.selection_model.model().file.duration
2737
+ ):
2738
+ reg.setRegion([beg, self.selection_model.model().file.duration])
2713
2739
  return
2714
2740
  else:
2715
2741
  if beg < 0:
2716
2742
  reg.setRegion([0, end])
2717
2743
  return
2718
- if end > self.selection_model.current_file.duration:
2719
- reg.setRegion([beg, self.selection_model.current_file.duration])
2744
+ if (
2745
+ self.selection_model.model().file is not None
2746
+ and end > self.selection_model.model().file.duration
2747
+ ):
2748
+ reg.setRegion([beg, self.selection_model.model().file.duration])
2720
2749
  return
2721
2750
  for r in self.visible_utterances.values():
2722
2751
  if r == reg:
@@ -2747,7 +2776,7 @@ class SpeakerTier(pg.GraphicsObject):
2747
2776
  new_end = round(end, 4)
2748
2777
  if new_begin == utt.begin and new_end == utt.end:
2749
2778
  return
2750
- self.corpus_model.update_utterance_times(utt, begin=new_begin, end=new_end)
2779
+ self.selection_model.model().update_utterance_times(utt, begin=new_begin, end=new_end)
2751
2780
  self.selection_model.select_audio(new_begin, None)
2752
2781
  reg.text.begin = new_begin
2753
2782
  reg.text.end = new_end