From 30a674575ec1b6fca3442b1c7ec1c1ce373d5c3c Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 1 Mar 2024 16:06:48 -0500 Subject: [PATCH 001/104] start, end, shift, intervals --- src/aligned_textgrid/sequences/sequences.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index 9865e18..f5938b8 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -384,6 +384,22 @@ def __repr__(self) -> str: return out_string # properties + @property + def start(self): + return self._start + + @start.setter + def start(self, time): + self._start = time + + @property + def end(self): + return self._end + + @end.setter + def end(self, time): + self._end = time + @property def sub_starts(self): if len(self.subset_list) > 0: @@ -411,6 +427,10 @@ def sub_labels(self): return lab_list else: return [] + + def _shift(self, increment): + self.start += increment + self.end += increment ## Fusion def fuse_rightwards( From 138b8e1118ce2fcc5bb89e8b669f4b22c66cd513 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 1 Mar 2024 16:06:57 -0500 Subject: [PATCH 002/104] start, end tier setters --- src/aligned_textgrid/sequences/tiers.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index e4d4bf0..fd2a6af 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -112,10 +112,27 @@ def __repr__(self): @property def starts(self): return np.array([x.start for x in self.sequence_list]) + + @starts.setter + def starts(self, times): + if not len(self.sequence_list) == len(times): + raise Exception("There aren't the same number of new start times as intervals") + + for t, i in zip(times, self.sequence_list): + i.start = t @property def ends(self): return np.array([x.end for x in self.sequence_list]) + + @starts.setter + def starts(self, times): + if not len(self.sequence_list) == len(times): + raise Exception("There aren't the same number of new start times as intervals") + + for t, i in zip(times, self.sequence_list): + i.end = t + @property def labels(self): From 756464e53a626bc1b0052ef9ee32b526c0203fc5 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 1 Mar 2024 17:18:49 -0500 Subject: [PATCH 003/104] typo fix --- src/aligned_textgrid/sequences/tiers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index fd2a6af..7987328 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -125,8 +125,8 @@ def starts(self, times): def ends(self): return np.array([x.end for x in self.sequence_list]) - @starts.setter - def starts(self, times): + @ends.setter + def ends(self, times): if not len(self.sequence_list) == len(times): raise Exception("There aren't the same number of new start times as intervals") From 69fecd7c3dc203ab270a46f860eef4bb7f99e684 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 1 Mar 2024 17:18:58 -0500 Subject: [PATCH 004/104] setter tests --- tests/test_tiers/test_SequenceTier.py | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_tiers/test_SequenceTier.py b/tests/test_tiers/test_SequenceTier.py index 2c20341..4b53d5a 100644 --- a/tests/test_tiers/test_SequenceTier.py +++ b/tests/test_tiers/test_SequenceTier.py @@ -125,6 +125,37 @@ def test_properties(self): orig_labels = [x.label for x in self.read_tg.tiers[0].entries] assert all([x in orig_labels for x in word_tier.labels]) + def test_time_setting(self): + word_tier1 = SequenceTier( + self.read_tg.tiers[0], + entry_class=self.MyWord + ) + + word_tier2 = SequenceTier( + self.read_tg.tiers[0], + entry_class=self.MyWord + ) + + n = len(word_tier1.sequence_list) + fake_times = np.linspace(0, 1, n) + + word_tier1.starts = fake_times + assert np.all(fake_times == word_tier1.starts) + assert not np.all(word_tier2.starts == word_tier1.starts) + + word_tier1.ends = fake_times + assert np.all(fake_times == word_tier1.ends) + assert not np.all(word_tier2.ends == word_tier1.ends) + + too_short = np.linspace(0, 1, n - 20) + with pytest.raises(Exception): + word_tier1.starts = too_short + + with pytest.raises(Exception): + word_tier1.ends = too_short + + + def test_in_get_len(self): word_tier = SequenceTier( self.read_tg.tiers[0], From 554a871559629d562a4eae63d0cb64f8b69d79eb Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 1 Mar 2024 18:01:55 -0500 Subject: [PATCH 005/104] tiershifts --- src/aligned_textgrid/sequences/tiers.py | 7 +++++-- tests/test_tiers/test_SequenceTier.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 7987328..95e96c5 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -132,8 +132,11 @@ def ends(self, times): for t, i in zip(times, self.sequence_list): i.end = t - - + + def _shift(self, increment): + self.starts += increment + self.ends += increment + @property def labels(self): return [x.label for x in self.sequence_list] diff --git a/tests/test_tiers/test_SequenceTier.py b/tests/test_tiers/test_SequenceTier.py index 4b53d5a..925e598 100644 --- a/tests/test_tiers/test_SequenceTier.py +++ b/tests/test_tiers/test_SequenceTier.py @@ -155,6 +155,24 @@ def test_time_setting(self): word_tier1.ends = too_short + def test_shift(self): + word_tier1 = SequenceTier( + self.read_tg.tiers[0], + entry_class=self.MyWord + ) + + word_tier2 = SequenceTier( + self.read_tg.tiers[0], + entry_class=self.MyWord + ) + + word_tier1._shift(5) + + s_shifts = word_tier1.starts - word_tier2.starts + e_shifts = word_tier1.ends - word_tier2.ends + + assert np.all(np.isclose(s_shifts, 5)) + assert np.all(np.isclose(e_shifts, 5)) def test_in_get_len(self): word_tier = SequenceTier( From ba8f05bbedab51a0fa954b8a7cfa3cad0c1eb3a4 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Sat, 2 Mar 2024 11:03:57 -0500 Subject: [PATCH 006/104] point shift --- src/aligned_textgrid/points/points.py | 11 +++++++++++ tests/test_sequences/test_points.py | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/src/aligned_textgrid/points/points.py b/src/aligned_textgrid/points/points.py index bec3191..f7aaa6b 100644 --- a/src/aligned_textgrid/points/points.py +++ b/src/aligned_textgrid/points/points.py @@ -75,6 +75,14 @@ def __getitem__(self, idex): return None ## Properties + @property + def time(self): + return self._time + + @time.setter + def time(self, time): + self._time = time + @property def fol_distance(self): if self.fol and self.fol.time: @@ -102,6 +110,9 @@ def prev_distance(self): return None ## methods + def _shift(self, increment): + self.time += increment + def distance_from( self, entry: Self|SequenceInterval diff --git a/tests/test_sequences/test_points.py b/tests/test_sequences/test_points.py index 3f603f2..7661759 100644 --- a/tests/test_sequences/test_points.py +++ b/tests/test_sequences/test_points.py @@ -56,6 +56,14 @@ def test_return(self): assert isinstance(out_point, Point) +class TestTiming: + + def test_shift(self): + seq_point_a = SequencePoint(Point(1, "a")) + + seq_point_a._shift(2) + assert seq_point_a.time == 3 + class TestDistances: seq_point_a = SequencePoint(Point(1, "a")) seq_point_b = SequencePoint(Point(2, "b")) From 39d35e28dd0e4228ac17dc3e636e39ae8ee6ba37 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Sat, 2 Mar 2024 11:11:54 -0500 Subject: [PATCH 007/104] time set point tier --- src/aligned_textgrid/points/tiers.py | 8 ++++++++ tests/test_tiers/test_PointTier.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/aligned_textgrid/points/tiers.py b/src/aligned_textgrid/points/tiers.py index c99feff..c412090 100644 --- a/src/aligned_textgrid/points/tiers.py +++ b/src/aligned_textgrid/points/tiers.py @@ -93,6 +93,14 @@ def times(self): [x.time for x in self.sequence_list] ) + @times.setter + def times(self, new_times): + if not len(self.sequence_list) == len(new_times): + raise Exception("There aren't the same number of new start times as intervals") + + for p, t in zip(self.sequence_list, new_times): + p.time = t + @property def labels(self): return [x.label for x in self.sequence_list] diff --git a/tests/test_tiers/test_PointTier.py b/tests/test_tiers/test_PointTier.py index 068dc22..1d331e2 100644 --- a/tests/test_tiers/test_PointTier.py +++ b/tests/test_tiers/test_PointTier.py @@ -81,6 +81,23 @@ def test_first_last_error(self): with pytest.raises(IndexError): tier.last +class TestPointTime: + def test_time_set(self): + point_a = Point(1, "a") + point_b = Point(2, "b") + point_c = Point(3, "c") + + tier = SequencePointTier(tier = [point_a, point_b, point_c]) + + tier.times += 1 + assert tier.times[0] == 2 + + tier.times = np.array([4, 5, 6]) + assert tier.times[0] == 4 + + with pytest.raises(Exception): + tier.times = np.array([6, 7]) + class TestPointGroup: point_a = Point(1, "a") point_b = Point(2, "b") From fde54ca7f87f7ca953942ada703c5ae768eb9f27 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Sat, 2 Mar 2024 11:15:08 -0500 Subject: [PATCH 008/104] point tier shift --- src/aligned_textgrid/points/tiers.py | 3 +++ tests/test_tiers/test_PointTier.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/aligned_textgrid/points/tiers.py b/src/aligned_textgrid/points/tiers.py index c412090..5a29e1b 100644 --- a/src/aligned_textgrid/points/tiers.py +++ b/src/aligned_textgrid/points/tiers.py @@ -118,6 +118,9 @@ def xmax(self): return self.sequence_list[-1].time else: return None + + def _shift(self, increment): + self.times += increment def get_nearest_point_index( self, diff --git a/tests/test_tiers/test_PointTier.py b/tests/test_tiers/test_PointTier.py index 1d331e2..2267b96 100644 --- a/tests/test_tiers/test_PointTier.py +++ b/tests/test_tiers/test_PointTier.py @@ -98,6 +98,19 @@ def test_time_set(self): with pytest.raises(Exception): tier.times = np.array([6, 7]) + def test_time_shift(self): + point_a = Point(1, "a") + point_b = Point(2, "b") + point_c = Point(3, "c") + + tier = SequencePointTier(tier = [point_a, point_b, point_c]) + + orig_times = tier.times + + tier._shift(3) + + assert all(np.isclose(tier.times - orig_times, 3)) + class TestPointGroup: point_a = Point(1, "a") point_b = Point(2, "b") From 5a12c34d532d17eb7e2305eab083977c1f4015c8 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Sat, 2 Mar 2024 11:40:21 -0500 Subject: [PATCH 009/104] tiergroup shift --- src/aligned_textgrid/sequences/tiers.py | 16 ++++++++++++++ tests/test_aligned_textgrid.py | 29 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 95e96c5..baf5e0e 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -317,6 +317,22 @@ def xmin(self): @property def xmax(self): return np.array([tier.xmax for tier in self.tier_list]).min() + + def shift( + self, + increment: float + ): + """Shift the start and end times of all intervals within + the TierGroup by the increment size + + Args: + increment (float): + The time increment by which to shift the + intervals within the TierGroup. Could be + positive or negative + """ + for tier in self.tier_list: + tier._shift(increment) def get_intervals_at_time( self, diff --git a/tests/test_aligned_textgrid.py b/tests/test_aligned_textgrid.py index fc4d3f7..ce7cd0d 100644 --- a/tests/test_aligned_textgrid.py +++ b/tests/test_aligned_textgrid.py @@ -249,6 +249,35 @@ def test_tiergroup_name(self): assert isinstance(tg.IVR, TierGroup) +class TestTierGroupShift: + def test_tiergroup_shift(self): + tg = AlignedTextGrid( + textgrid_path="tests/test_data/KY25A_1.TextGrid", + entry_classes=[Word, Phone] + ) + + tgr = tg[0] + orig_starts = [ + tier.starts for tier in tgr + ] + orig_ends = [ + tier.ends for tier in tgr + ] + + tgr.shift(3) + + new_starts = [ + tier.starts for tier in tgr + ] + new_ends = [ + tier.ends for tier in tgr + ] + + for o, n in zip(orig_starts, new_starts): + assert np.all(np.isclose(n-o, 3)) + for o, n in zip(orig_ends, new_ends): + assert np.all(np.isclose(n-o, 3)) + class TestInterleave: From b355f2b5777d6e9532056cff82b8df50e7fd65c1 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Sat, 2 Mar 2024 11:49:38 -0500 Subject: [PATCH 010/104] PointsGroup shift --- src/aligned_textgrid/points/tiers.py | 17 +++++++++++++++ tests/test_tiers/test_PointTier.py | 32 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/aligned_textgrid/points/tiers.py b/src/aligned_textgrid/points/tiers.py index 5a29e1b..f72e989 100644 --- a/src/aligned_textgrid/points/tiers.py +++ b/src/aligned_textgrid/points/tiers.py @@ -195,6 +195,23 @@ def __init__( super().__init__() self.tier_list = tiers self.contains = self.tier_list + + def shift( + self, + increment: float + ): + """Shift the times of all points within + the PointsGroup by the increment size + + Args: + increment (float): + The time increment by which to shift the + points within the PointsGroup. Could be + positive or negative + """ + + for tier in self.tier_list: + tier._shift(increment) def get_nearest_points_index( self, diff --git a/tests/test_tiers/test_PointTier.py b/tests/test_tiers/test_PointTier.py index 2267b96..37baa25 100644 --- a/tests/test_tiers/test_PointTier.py +++ b/tests/test_tiers/test_PointTier.py @@ -159,7 +159,39 @@ def test_nearest(self): nearest = point_group.get_nearest_points_index(1.25) assert len(nearest) == 2 + +class TestPointGroupShift: + point_a = Point(1, "a") + point_b = Point(2, "b") + point_c = Point(1.5, "c") + point_d = Point(2.5, "d") + + point_tier1 = PointTier(name = "test1", entries = [point_a, point_b]) + point_tier2 = PointTier(name = "test2", entries = [point_c, point_d]) + + seq_point_tier1 = SequencePointTier(point_tier1) + seq_point_tier2 = SequencePointTier(point_tier2) + + point_group = PointsGroup( + [seq_point_tier1, seq_point_tier2] + ) + + def test_shift(self): + orig_times = [ + tier.times + for tier in self.point_group.tier_list + ] + + self.point_group.shift(3) + + new_times = [ + tier.times + for tier in self.point_group.tier_list + ] + for o, n in zip(orig_times, new_times): + assert np.all(np.isclose(n-o, 3)) + class TestAccessors: class MyPointClassA(SequencePoint): def __init__(self, point): From 0b05a730646783128c9ed696dc2a6ee7d1f0e263 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Sat, 2 Mar 2024 11:56:28 -0500 Subject: [PATCH 011/104] entire textgrid shift --- src/aligned_textgrid/aligned_textgrid.py | 15 ++++++++++ tests/test_aligned_textgrid.py | 36 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/aligned_textgrid/aligned_textgrid.py b/src/aligned_textgrid/aligned_textgrid.py index 72ac9ee..2e8b611 100644 --- a/src/aligned_textgrid/aligned_textgrid.py +++ b/src/aligned_textgrid/aligned_textgrid.py @@ -294,6 +294,21 @@ def xmax(self): raise ValueError('No maximum time for empty TextGrid.') return np.array([tgroup.xmax for tgroup in self.tier_groups]).max() + def shift( + self, + increment: float + ): + """Shift all times (interval starts & ends and point times) + by the given increment. + + Args: + increment (float): + The increment by which to shift all times. + Could be positive or negative. + """ + for gr in self: + gr.shift(increment) + def interleave_class( self, name:str, diff --git a/tests/test_aligned_textgrid.py b/tests/test_aligned_textgrid.py index ce7cd0d..0247261 100644 --- a/tests/test_aligned_textgrid.py +++ b/tests/test_aligned_textgrid.py @@ -448,3 +448,39 @@ def test_exceptions(self): below = Word, timing_from="Word" ) + +class TestTextGridShift: + + def test_positive_shift(self): + tg = AlignedTextGrid( + textgrid_path="tests/test_data/KY25A_1.TextGrid", + entry_classes=custom_classes(["Word", "Phone"]) + ) + + orig_xmin = tg.xmin + orig_xmax = tg.xmax + + tg.shift(3) + + new_xmin = tg.xmin + new_xmax = tg.xmax + + assert np.isclose(new_xmin - orig_xmin, 3) + assert np.isclose(new_xmax - orig_xmax, 3) + + def test_negative_shift(self): + tg = AlignedTextGrid( + textgrid_path="tests/test_data/KY25A_1.TextGrid", + entry_classes=custom_classes(["Word", "Phone"]) + ) + + orig_xmin = tg.xmin + orig_xmax = tg.xmax + + tg.shift(-3) + + new_xmin = tg.xmin + new_xmax = tg.xmax + + assert np.isclose(new_xmin - orig_xmin, -3) + assert np.isclose(new_xmax - orig_xmax, -3) From f683ed8ba8ee64e72cf5649072be62e6ff5d3d65 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Sat, 2 Mar 2024 11:58:32 -0500 Subject: [PATCH 012/104] docs update --- doc_src/objects.json | 2 +- doc_src/reference/AlignedTextGrid.qmd | 31 ++++++++++++++++++++------- doc_src/reference/TierGroup.qmd | 14 ++++++++++++ 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/doc_src/objects.json b/doc_src/objects.json index a423b1f..5912efd 100644 --- a/doc_src/objects.json +++ b/doc_src/objects.json @@ -1 +1 @@ -{"project": "aligned_textgrid", "version": "0.0.9999", "count": 55, "items": [{"name": "aligned_textgrid.mixins.mixins.PrecedenceMixins.set_final", "domain": "py", "role": "function", "priority": "1", "uri": "reference/PrecedenceMixins.html#aligned_textgrid.mixins.mixins.PrecedenceMixins.set_final", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.PrecedenceMixins.set_fol", "domain": "py", "role": "function", "priority": "1", "uri": "reference/PrecedenceMixins.html#aligned_textgrid.mixins.mixins.PrecedenceMixins.set_fol", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.PrecedenceMixins.set_initial", "domain": "py", "role": "function", "priority": "1", "uri": "reference/PrecedenceMixins.html#aligned_textgrid.mixins.mixins.PrecedenceMixins.set_initial", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.PrecedenceMixins.set_prev", "domain": "py", "role": "function", "priority": "1", "uri": "reference/PrecedenceMixins.html#aligned_textgrid.mixins.mixins.PrecedenceMixins.set_prev", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.PrecedenceMixins", "domain": "py", "role": "class", "priority": "1", "uri": "reference/PrecedenceMixins.html#aligned_textgrid.mixins.mixins.PrecedenceMixins", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.InTierMixins.get_tierwise", "domain": "py", "role": "function", "priority": "1", "uri": "reference/InTierMixins.html#aligned_textgrid.mixins.mixins.InTierMixins.get_tierwise", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.InTierMixins.return_interval", "domain": "py", "role": "function", "priority": "1", "uri": "reference/InTierMixins.html#aligned_textgrid.mixins.mixins.InTierMixins.return_interval", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.InTierMixins.return_point", "domain": "py", "role": "function", "priority": "1", "uri": "reference/InTierMixins.html#aligned_textgrid.mixins.mixins.InTierMixins.return_point", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.InTierMixins", "domain": "py", "role": "class", "priority": "1", "uri": "reference/InTierMixins.html#aligned_textgrid.mixins.mixins.InTierMixins", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval.fuse_leftwards", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval.fuse_leftwards", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval.fuse_rightwards", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval.fuse_rightwards", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval.index", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval.index", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval.pop", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval.pop", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval.set_feature", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval.set_feature", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval", "domain": "py", "role": "class", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.Top", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Top.html#aligned_textgrid.sequences.sequences.Top", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.Bottom", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Bottom.html#aligned_textgrid.sequences.sequences.Bottom", "dispname": "-"}, {"name": "aligned_textgrid.points.points.SequencePoint.distance_from", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePoint.html#aligned_textgrid.points.points.SequencePoint.distance_from", "dispname": "-"}, {"name": "aligned_textgrid.points.points.SequencePoint.get_interval_at_point", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePoint.html#aligned_textgrid.points.points.SequencePoint.get_interval_at_point", "dispname": "-"}, {"name": "aligned_textgrid.points.points.SequencePoint.get_interval_index_at_time", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePoint.html#aligned_textgrid.points.points.SequencePoint.get_interval_index_at_time", "dispname": "-"}, {"name": "aligned_textgrid.points.points.SequencePoint", "domain": "py", "role": "class", "priority": "1", "uri": "reference/SequencePoint.html#aligned_textgrid.points.points.SequencePoint", "dispname": "-"}, {"name": "aligned_textgrid.mixins.tiermixins.TierMixins.index", "domain": "py", "role": "function", "priority": "1", "uri": "reference/TierMixins.html#aligned_textgrid.mixins.tiermixins.TierMixins.index", "dispname": "-"}, {"name": "aligned_textgrid.mixins.tiermixins.TierMixins", "domain": "py", "role": "class", "priority": "1", "uri": "reference/TierMixins.html#aligned_textgrid.mixins.tiermixins.TierMixins", "dispname": "-"}, {"name": "aligned_textgrid.mixins.tiermixins.TierGroupMixins", "domain": "py", "role": "class", "priority": "1", "uri": "reference/TierGroupMixins.html#aligned_textgrid.mixins.tiermixins.TierGroupMixins", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.SequenceTier.get_interval_at_time", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceTier.html#aligned_textgrid.sequences.tiers.SequenceTier.get_interval_at_time", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.SequenceTier.pop", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceTier.html#aligned_textgrid.sequences.tiers.SequenceTier.pop", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.SequenceTier.return_tier", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceTier.html#aligned_textgrid.sequences.tiers.SequenceTier.return_tier", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.SequenceTier.save_as_tg", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceTier.html#aligned_textgrid.sequences.tiers.SequenceTier.save_as_tg", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.SequenceTier", "domain": "py", "role": "class", "priority": "1", "uri": "reference/SequenceTier.html#aligned_textgrid.sequences.tiers.SequenceTier", "dispname": "-"}, {"name": "aligned_textgrid.points.tiers.SequencePointTier.get_nearest_point", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePointTier.html#aligned_textgrid.points.tiers.SequencePointTier.get_nearest_point", "dispname": "-"}, {"name": "aligned_textgrid.points.tiers.SequencePointTier.get_nearest_point_index", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePointTier.html#aligned_textgrid.points.tiers.SequencePointTier.get_nearest_point_index", "dispname": "-"}, {"name": "aligned_textgrid.points.tiers.SequencePointTier.return_tier", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePointTier.html#aligned_textgrid.points.tiers.SequencePointTier.return_tier", "dispname": "-"}, {"name": "aligned_textgrid.points.tiers.SequencePointTier.save_as_tg", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePointTier.html#aligned_textgrid.points.tiers.SequencePointTier.save_as_tg", "dispname": "-"}, {"name": "aligned_textgrid.points.tiers.SequencePointTier", "domain": "py", "role": "class", "priority": "1", "uri": "reference/SequencePointTier.html#aligned_textgrid.points.tiers.SequencePointTier", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.TierGroup.get_intervals_at_time", "domain": "py", "role": "function", "priority": "1", "uri": "reference/TierGroup.html#aligned_textgrid.sequences.tiers.TierGroup.get_intervals_at_time", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.TierGroup.show_structure", "domain": "py", "role": "function", "priority": "1", "uri": "reference/TierGroup.html#aligned_textgrid.sequences.tiers.TierGroup.show_structure", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.TierGroup", "domain": "py", "role": "class", "priority": "1", "uri": "reference/TierGroup.html#aligned_textgrid.sequences.tiers.TierGroup", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.get_class_by_name", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.get_class_by_name", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.get_intervals_at_time", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.get_intervals_at_time", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.interleave_class", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.interleave_class", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.return_textgrid", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.return_textgrid", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.save_textgrid", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.save_textgrid", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid", "domain": "py", "role": "class", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid", "dispname": "-"}, {"name": "aligned_textgrid.sequences.word_and_phone.Word.set_phones", "domain": "py", "role": "function", "priority": "1", "uri": "reference/Word.html#aligned_textgrid.sequences.word_and_phone.Word.set_phones", "dispname": "-"}, {"name": "aligned_textgrid.sequences.word_and_phone.Word", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Word.html#aligned_textgrid.sequences.word_and_phone.Word", "dispname": "-"}, {"name": "aligned_textgrid.sequences.word_and_phone.Phone.set_word", "domain": "py", "role": "function", "priority": "1", "uri": "reference/Phone.html#aligned_textgrid.sequences.word_and_phone.Phone.set_word", "dispname": "-"}, {"name": "aligned_textgrid.sequences.word_and_phone.Phone", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Phone.html#aligned_textgrid.sequences.word_and_phone.Phone", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.PrStr", "domain": "py", "role": "class", "priority": "1", "uri": "reference/PrStr.html#aligned_textgrid.polar.polar_classes.PrStr", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.ToBI", "domain": "py", "role": "class", "priority": "1", "uri": "reference/ToBI.html#aligned_textgrid.polar.polar_classes.ToBI", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.ToBI", "domain": "py", "role": "class", "priority": "1", "uri": "reference/ToBI.html#aligned_textgrid.polar.polar_classes.ToBI", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.TurningPoints", "domain": "py", "role": "class", "priority": "1", "uri": "reference/TurningPoints.html#aligned_textgrid.polar.polar_classes.TurningPoints", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.Ranges", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Ranges.html#aligned_textgrid.polar.polar_classes.Ranges", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.Levels", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Levels.html#aligned_textgrid.polar.polar_classes.Levels", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.Misc", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Misc.html#aligned_textgrid.polar.polar_classes.Misc", "dispname": "-"}, {"name": "aligned_textgrid.outputs.to_dataframe.to_df", "domain": "py", "role": "function", "priority": "1", "uri": "reference/to_df.html#aligned_textgrid.outputs.to_dataframe.to_df", "dispname": "-"}]} \ No newline at end of file +{"project": "aligned_textgrid", "version": "0.0.9999", "count": 57, "items": [{"name": "aligned_textgrid.mixins.mixins.PrecedenceMixins.set_final", "domain": "py", "role": "function", "priority": "1", "uri": "reference/PrecedenceMixins.html#aligned_textgrid.mixins.mixins.PrecedenceMixins.set_final", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.PrecedenceMixins.set_fol", "domain": "py", "role": "function", "priority": "1", "uri": "reference/PrecedenceMixins.html#aligned_textgrid.mixins.mixins.PrecedenceMixins.set_fol", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.PrecedenceMixins.set_initial", "domain": "py", "role": "function", "priority": "1", "uri": "reference/PrecedenceMixins.html#aligned_textgrid.mixins.mixins.PrecedenceMixins.set_initial", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.PrecedenceMixins.set_prev", "domain": "py", "role": "function", "priority": "1", "uri": "reference/PrecedenceMixins.html#aligned_textgrid.mixins.mixins.PrecedenceMixins.set_prev", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.PrecedenceMixins", "domain": "py", "role": "class", "priority": "1", "uri": "reference/PrecedenceMixins.html#aligned_textgrid.mixins.mixins.PrecedenceMixins", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.InTierMixins.get_tierwise", "domain": "py", "role": "function", "priority": "1", "uri": "reference/InTierMixins.html#aligned_textgrid.mixins.mixins.InTierMixins.get_tierwise", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.InTierMixins.return_interval", "domain": "py", "role": "function", "priority": "1", "uri": "reference/InTierMixins.html#aligned_textgrid.mixins.mixins.InTierMixins.return_interval", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.InTierMixins.return_point", "domain": "py", "role": "function", "priority": "1", "uri": "reference/InTierMixins.html#aligned_textgrid.mixins.mixins.InTierMixins.return_point", "dispname": "-"}, {"name": "aligned_textgrid.mixins.mixins.InTierMixins", "domain": "py", "role": "class", "priority": "1", "uri": "reference/InTierMixins.html#aligned_textgrid.mixins.mixins.InTierMixins", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval.fuse_leftwards", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval.fuse_leftwards", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval.fuse_rightwards", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval.fuse_rightwards", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval.index", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval.index", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval.pop", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval.pop", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval.set_feature", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval.set_feature", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.SequenceInterval", "domain": "py", "role": "class", "priority": "1", "uri": "reference/SequenceInterval.html#aligned_textgrid.sequences.sequences.SequenceInterval", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.Top", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Top.html#aligned_textgrid.sequences.sequences.Top", "dispname": "-"}, {"name": "aligned_textgrid.sequences.sequences.Bottom", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Bottom.html#aligned_textgrid.sequences.sequences.Bottom", "dispname": "-"}, {"name": "aligned_textgrid.points.points.SequencePoint.distance_from", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePoint.html#aligned_textgrid.points.points.SequencePoint.distance_from", "dispname": "-"}, {"name": "aligned_textgrid.points.points.SequencePoint.get_interval_at_point", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePoint.html#aligned_textgrid.points.points.SequencePoint.get_interval_at_point", "dispname": "-"}, {"name": "aligned_textgrid.points.points.SequencePoint.get_interval_index_at_time", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePoint.html#aligned_textgrid.points.points.SequencePoint.get_interval_index_at_time", "dispname": "-"}, {"name": "aligned_textgrid.points.points.SequencePoint", "domain": "py", "role": "class", "priority": "1", "uri": "reference/SequencePoint.html#aligned_textgrid.points.points.SequencePoint", "dispname": "-"}, {"name": "aligned_textgrid.mixins.tiermixins.TierMixins.index", "domain": "py", "role": "function", "priority": "1", "uri": "reference/TierMixins.html#aligned_textgrid.mixins.tiermixins.TierMixins.index", "dispname": "-"}, {"name": "aligned_textgrid.mixins.tiermixins.TierMixins", "domain": "py", "role": "class", "priority": "1", "uri": "reference/TierMixins.html#aligned_textgrid.mixins.tiermixins.TierMixins", "dispname": "-"}, {"name": "aligned_textgrid.mixins.tiermixins.TierGroupMixins", "domain": "py", "role": "class", "priority": "1", "uri": "reference/TierGroupMixins.html#aligned_textgrid.mixins.tiermixins.TierGroupMixins", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.SequenceTier.get_interval_at_time", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceTier.html#aligned_textgrid.sequences.tiers.SequenceTier.get_interval_at_time", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.SequenceTier.pop", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceTier.html#aligned_textgrid.sequences.tiers.SequenceTier.pop", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.SequenceTier.return_tier", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceTier.html#aligned_textgrid.sequences.tiers.SequenceTier.return_tier", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.SequenceTier.save_as_tg", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequenceTier.html#aligned_textgrid.sequences.tiers.SequenceTier.save_as_tg", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.SequenceTier", "domain": "py", "role": "class", "priority": "1", "uri": "reference/SequenceTier.html#aligned_textgrid.sequences.tiers.SequenceTier", "dispname": "-"}, {"name": "aligned_textgrid.points.tiers.SequencePointTier.get_nearest_point", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePointTier.html#aligned_textgrid.points.tiers.SequencePointTier.get_nearest_point", "dispname": "-"}, {"name": "aligned_textgrid.points.tiers.SequencePointTier.get_nearest_point_index", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePointTier.html#aligned_textgrid.points.tiers.SequencePointTier.get_nearest_point_index", "dispname": "-"}, {"name": "aligned_textgrid.points.tiers.SequencePointTier.return_tier", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePointTier.html#aligned_textgrid.points.tiers.SequencePointTier.return_tier", "dispname": "-"}, {"name": "aligned_textgrid.points.tiers.SequencePointTier.save_as_tg", "domain": "py", "role": "function", "priority": "1", "uri": "reference/SequencePointTier.html#aligned_textgrid.points.tiers.SequencePointTier.save_as_tg", "dispname": "-"}, {"name": "aligned_textgrid.points.tiers.SequencePointTier", "domain": "py", "role": "class", "priority": "1", "uri": "reference/SequencePointTier.html#aligned_textgrid.points.tiers.SequencePointTier", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.TierGroup.get_intervals_at_time", "domain": "py", "role": "function", "priority": "1", "uri": "reference/TierGroup.html#aligned_textgrid.sequences.tiers.TierGroup.get_intervals_at_time", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.TierGroup.shift", "domain": "py", "role": "function", "priority": "1", "uri": "reference/TierGroup.html#aligned_textgrid.sequences.tiers.TierGroup.shift", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.TierGroup.show_structure", "domain": "py", "role": "function", "priority": "1", "uri": "reference/TierGroup.html#aligned_textgrid.sequences.tiers.TierGroup.show_structure", "dispname": "-"}, {"name": "aligned_textgrid.sequences.tiers.TierGroup", "domain": "py", "role": "class", "priority": "1", "uri": "reference/TierGroup.html#aligned_textgrid.sequences.tiers.TierGroup", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.get_class_by_name", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.get_class_by_name", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.get_intervals_at_time", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.get_intervals_at_time", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.interleave_class", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.interleave_class", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.return_textgrid", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.return_textgrid", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.save_textgrid", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.save_textgrid", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid.shift", "domain": "py", "role": "function", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid.shift", "dispname": "-"}, {"name": "aligned_textgrid.aligned_textgrid.AlignedTextGrid", "domain": "py", "role": "class", "priority": "1", "uri": "reference/AlignedTextGrid.html#aligned_textgrid.aligned_textgrid.AlignedTextGrid", "dispname": "-"}, {"name": "aligned_textgrid.sequences.word_and_phone.Word.set_phones", "domain": "py", "role": "function", "priority": "1", "uri": "reference/Word.html#aligned_textgrid.sequences.word_and_phone.Word.set_phones", "dispname": "-"}, {"name": "aligned_textgrid.sequences.word_and_phone.Word", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Word.html#aligned_textgrid.sequences.word_and_phone.Word", "dispname": "-"}, {"name": "aligned_textgrid.sequences.word_and_phone.Phone.set_word", "domain": "py", "role": "function", "priority": "1", "uri": "reference/Phone.html#aligned_textgrid.sequences.word_and_phone.Phone.set_word", "dispname": "-"}, {"name": "aligned_textgrid.sequences.word_and_phone.Phone", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Phone.html#aligned_textgrid.sequences.word_and_phone.Phone", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.PrStr", "domain": "py", "role": "class", "priority": "1", "uri": "reference/PrStr.html#aligned_textgrid.polar.polar_classes.PrStr", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.ToBI", "domain": "py", "role": "class", "priority": "1", "uri": "reference/ToBI.html#aligned_textgrid.polar.polar_classes.ToBI", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.ToBI", "domain": "py", "role": "class", "priority": "1", "uri": "reference/ToBI.html#aligned_textgrid.polar.polar_classes.ToBI", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.TurningPoints", "domain": "py", "role": "class", "priority": "1", "uri": "reference/TurningPoints.html#aligned_textgrid.polar.polar_classes.TurningPoints", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.Ranges", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Ranges.html#aligned_textgrid.polar.polar_classes.Ranges", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.Levels", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Levels.html#aligned_textgrid.polar.polar_classes.Levels", "dispname": "-"}, {"name": "aligned_textgrid.polar.polar_classes.Misc", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Misc.html#aligned_textgrid.polar.polar_classes.Misc", "dispname": "-"}, {"name": "aligned_textgrid.outputs.to_dataframe.to_df", "domain": "py", "role": "function", "priority": "1", "uri": "reference/to_df.html#aligned_textgrid.outputs.to_dataframe.to_df", "dispname": "-"}]} \ No newline at end of file diff --git a/doc_src/reference/AlignedTextGrid.qmd b/doc_src/reference/AlignedTextGrid.qmd index 26c1304..dbb0b41 100644 --- a/doc_src/reference/AlignedTextGrid.qmd +++ b/doc_src/reference/AlignedTextGrid.qmd @@ -14,13 +14,14 @@ An aligned Textgrid ## Attributes -| Name | Type | Description | -|---------------|----------------------------------------------|------------------------------------------------------| -| entry_classes | list\[Sequence\[Type\[SequenceInterval\]\]\] | The entry classes for each tier within a tier group. | -| tier_groups | list\[TierGroup\] | a list of `TierGroup` | -| xmax | float | Maximum time | -| xmin | float | Minimum time | -| \[\] | | indexable | +| Name | Type | Description | +|---------------|----------------------------------------------------------|------------------------------------------------------| +| entry_classes | list\[Sequence\[Type\[SequenceInterval\]\]\] \| list\[\] | The entry classes for each tier within a tier group. | +| tier_groups | list\[TierGroup\] \| list\[\] | A list of `TierGroup` or an empty list. | +| tier_names | list\[str\] | A list of names for tiers in tier_groups. | +| xmax | float | Maximum time | +| xmin | float | Minimum time | +| \[\] | | indexable | ## Methods @@ -31,6 +32,7 @@ An aligned Textgrid | [interleave_class](#aligned_textgrid.aligned_textgrid.AlignedTextGrid.interleave_class) | Interleave a new entry class. | | [return_textgrid](#aligned_textgrid.aligned_textgrid.AlignedTextGrid.return_textgrid) | Convert this `AlignedTextGrid` to a `praatio` `Textgrid` | | [save_textgrid](#aligned_textgrid.aligned_textgrid.AlignedTextGrid.save_textgrid) | Saves the current AlignedTextGrid | +| [shift](#aligned_textgrid.aligned_textgrid.AlignedTextGrid.shift) | Shift all times (interval starts & ends and point times) | ### get_class_by_name { #aligned_textgrid.aligned_textgrid.AlignedTextGrid.get_class_by_name } @@ -116,4 +118,17 @@ Uses the `praatio.data_classes.textgrid.Textgrid.save()` method. | Name | Type | Description | Default | |-------------|-----------------------------------------------------------------------|------------------------------|-------------------| | `save_path` | str | path for saving the textgrid | _required_ | -| `format` | Literal\['short_textgrid', 'long_textgrid', 'json', 'textgrid_json'\] | Save format. | `'long_textgrid'` | \ No newline at end of file +| `format` | Literal\['short_textgrid', 'long_textgrid', 'json', 'textgrid_json'\] | Save format. | `'long_textgrid'` | + +### shift { #aligned_textgrid.aligned_textgrid.AlignedTextGrid.shift } + +`aligned_textgrid.AlignedTextGrid.shift(increment)` + +Shift all times (interval starts & ends and point times) +by the given increment. + +#### Parameters + +| Name | Type | Description | Default | +|-------------|--------|---------------------------------------------------------------------------|------------| +| `increment` | float | The increment by which to shift all times. Could be positive or negative. | _required_ | \ No newline at end of file diff --git a/doc_src/reference/TierGroup.qmd b/doc_src/reference/TierGroup.qmd index 8d4c83c..0e2998e 100644 --- a/doc_src/reference/TierGroup.qmd +++ b/doc_src/reference/TierGroup.qmd @@ -26,6 +26,7 @@ Tier Grouping | Name | Description | | --- | --- | | [get_intervals_at_time](#aligned_textgrid.sequences.tiers.TierGroup.get_intervals_at_time) | Get intervals at time | +| [shift](#aligned_textgrid.sequences.tiers.TierGroup.shift) | Shift the start and end times of all intervals within | | [show_structure](#aligned_textgrid.sequences.tiers.TierGroup.show_structure) | Show the hierarchical structure | ### get_intervals_at_time { #aligned_textgrid.sequences.tiers.TierGroup.get_intervals_at_time } @@ -48,6 +49,19 @@ Returns a list of intervals at `time` for each tier. |-------------|--------------------------------------------------------------| | list\[int\] | A list of interval indices, one for each tier in `tier_list` | +### shift { #aligned_textgrid.sequences.tiers.TierGroup.shift } + +`sequences.tiers.TierGroup.shift(increment)` + +Shift the start and end times of all intervals within +the TierGroup by the increment size + +#### Parameters + +| Name | Type | Description | Default | +|-------------|--------|--------------------------------------------------------------------------------------------------------|------------| +| `increment` | float | The time increment by which to shift the intervals within the TierGroup. Could be positive or negative | _required_ | + ### show_structure { #aligned_textgrid.sequences.tiers.TierGroup.show_structure } `sequences.tiers.TierGroup.show_structure()` From 59ae99af54be848d6a41bedbac31ab1e442d2aa3 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Sat, 2 Mar 2024 12:18:47 -0500 Subject: [PATCH 013/104] SequenceInterval init refactor --- src/aligned_textgrid/sequences/tiers.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index baf5e0e..fd613d5 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -50,9 +50,19 @@ def __init__( else: self.entry_list = tier self.name = entry_class.__name__ + self.__set_classes(entry_class) + self.__build_sequence_list() + self.__set_precedence() + + def __set_classes( + self, + entry_class + ): self.entry_class = entry_class self.superset_class = self.entry_class.superset_class self.subset_class = self.entry_class.subset_class + + def __build_sequence_list(self): entry_order = np.argsort([x.start for x in self.entry_list]) self.entry_list = [self.entry_list[idx] for idx in entry_order] self.sequence_list = [] @@ -61,7 +71,6 @@ def __init__( this_seq.set_superset_class(self.superset_class) this_seq.set_subset_class(self.subset_class) self.sequence_list += [this_seq] - self.__set_precedence() def __set_precedence(self): for idx,seq in enumerate(self.sequence_list): From 2c4856ca4f194d6dd17d4c70a5ad84979febae28 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 13:32:08 -0400 Subject: [PATCH 014/104] new sequence list class --- src/aligned_textgrid/sequences/sequences.py | 114 ++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index f0c79e0..fa32b5e 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -262,6 +262,120 @@ class HierarchyPart(HierarchyMixins): def __init__(self): pass +class IntervalList(Sequence): + """A list of SequenceIntervals that + remains sorted + + Args: + *args (SequenceInterval): + SequenceIntervals + + Attributes: + starts (np.array): + An array of start times + ends (np.array): + An array of end times + labels (list[str]): + A list of labels + """ + + def __init__(self, *args): + self._values = [] + self.entry_class = None + for arg in args: + self.append(arg) + + def __getitem__(self, idx): + return self._values[idx] + + def __len__(self): + return len(self._values) + + def __add__(self, other:Sequence): + unique_other_classes = set([x.__class__ for x in other]) + + if len(unique_other_classes) > 1: + raise ValueError("All values in added list must have the same class.") + + if len(unique_other_classes) < 1: + return + + incoming_class = next(iter(unique_other_classes)) + + if not incoming_class is self.entry_class: + raise ValueError("All values in added list must have the same class as original list.") + + return IntervalList(*(self._values + [x for x in other])) + + def __repr__(self): + return self._values.__repr__() + + def _sort(self): + if len(self._values) > 0: + item_starts = np.array([x.start for x in self._values]) + item_order = np.argsort(item_starts) + self._values = [self._values[idx] for idx in item_order] + + + @property + def starts(self)->np.array: + if len(self) > 0: + return np.array([x.start for x in self]) + + return np.array([]) + + @property + def ends(self) -> np.array: + if len(self) > 0: + return np.array([x.end for x in self]) + + return np.array([]) + + @property + def labels(self) -> list[str]: + if len(self) > 0: + return [x.label for x in self] + + return [] + + def append(self, value): + """Append a SequenceInterval to the list. + + After appending, the SequenceIntervals are re-sorted + + Args: + value (SequenceInterval): + A SequenceInterval to append + """ + if self.entry_class is None: + self.entry_class = value.__class__ + + if not self.entry_class is value.__class__: + raise ValueError("All values must have the same class.") + + self._values.append(value) + self._sort() + + def remove(self, x): + """Remove a SequenceInterval from the list + + Args: + x (SequenceInterval): + The SequenceInterval to remove. + """ + self._values.remove(x) + + def pop(self, x): + """Pop a SequneceInterval + + Args: + x (SequenceInterval): + SequenceInterval to pop + """ + if x in self: + pop_idx = self.index(x) + self._values.pop(pop_idx) + class SequenceInterval(InstanceMixins, InTierMixins, PrecedenceMixins, HierarchyPart): """ A class to describe an interval with precedence relationships and hierarchical relationships From 2f38ed75496f1e05fa03d8c8d8c8101f69157450 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 13:33:05 -0400 Subject: [PATCH 015/104] Rolling out new SequenceList --- src/aligned_textgrid/sequences/sequences.py | 82 +++++++++------------ tests/test_sequences/test_sequences.py | 2 +- 2 files changed, 35 insertions(+), 49 deletions(-) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index fa32b5e..53e97e6 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -117,11 +117,15 @@ def set_subset_list(self, subset_list = None): set as the `super_instance` of all objects in the list. """ - self.subset_list = [] + self._subset_list = IntervalList() + if subset_list is None: + return if all([isinstance(subint, self.subset_class) for subint in subset_list]): for element in subset_list: self.append_subset_list(element) + self._set_within() self._set_subset_precedence() + else: subset_class_set = set([type(x).__name__ for x in subset_list]) raise Exception(f"The subset_class was defined as {self.subset_class.__name__}, but provided subset_list contained {subset_class_set}") @@ -137,10 +141,10 @@ def append_subset_list(self, subset_instance = None): `subset_list` are reset. """ - if isinstance(subset_instance, self.subset_class) and not subset_instance in self.subset_list: + if isinstance(subset_instance, self.subset_class) and not subset_instance in self._subset_list: if subset_instance.super_instance: subset_instance.remove_superset() - self.subset_list.append(subset_instance) + self._subset_list.append(subset_instance) self._set_subset_precedence() # avoid recursion if not self is subset_instance.super_instance: @@ -156,11 +160,11 @@ def remove_from_subset_list(self, subset_instance = None): Args: subset_instance (SequenceInterval): The sequence interval to remove. """ - if subset_instance not in self.subset_list: + if subset_instance not in self._subset_list: #warnings.warn("Provided subset_instance was not in the subset list") return - self.subset_list.remove(subset_instance) + self._subset_list.remove(subset_instance) subset_instance.super_instance = None subset_instance.within = None self._set_subset_precedence() @@ -181,28 +185,21 @@ def _set_subset_precedence(self): Private method. Sorts subset list and re-sets precedence relationshops. """ - - self._sort_subsetlist() - for idx, p in enumerate(self.subset_list): + for idx, p in enumerate(self._subset_list): if idx == 0: p.set_initial() else: - p.set_prev(self.subset_list[idx-1]) - if idx == len(self.subset_list)-1: + p.set_prev(self._subset_list[idx-1]) + if idx == len(self._subset_list)-1: p.set_final() else: - p.set_fol(self.subset_list[idx+1]) + p.set_fol(self._subset_list[idx+1]) - def _sort_subsetlist(self): + def _set_within(self): """summary - Private method. Sorts the subset_list + Private method. Sets within """ - if len(self.subset_list) > 0: - item_starts = self.sub_starts - item_order = np.argsort(item_starts) - self.subset_list = [self.subset_list[idx] for idx in item_order] - - self.contains = self.subset_list + self.contains = self._subset_list ## Subset Validation def validate(self) -> bool: @@ -480,7 +477,7 @@ def __init__( if self.label != "#": self.set_initial() - self.subset_list = [] + self._subset_list = IntervalList() self.super_instance= None self.intier = None @@ -530,12 +527,8 @@ def pop( Args: subset_instance (SequenceInterval): A sequence interval to pop """ - if subset_instance in self.subset_list: - pop_idx = self.index(subset_instance) - self.subset_list.pop(pop_idx) - self._set_subset_precedence() - else: - raise Exception("Subset instance not in subset list") + self.subset_list.pop(subset_instance) + self._set_subset_precedence() def __repr__(self) -> str: out_string = f"Class {type(self).__name__}, label: {self.label}" @@ -556,6 +549,15 @@ def __repr__(self) -> str: return out_string # properties + @property + def subset_list(self): + return self._subset_list + + @subset_list.setter + def subset_list(self, intervals: list[Self]|IntervalList[Self]): + intervals = IntervalList(*intervals) + self.set_subset_list(intervals) + @property def start(self): return self._start @@ -574,32 +576,16 @@ def end(self, time): @property def sub_starts(self): - if len(self.subset_list) > 0: - start_arr = np.array([ - seg.start for seg in self.subset_list - ]) - return start_arr - else: - return np.array([]) + return self.subset_list.starts @property def sub_ends(self): - if len(self.subset_list) > 0: - end_arr = np.array([ - seg.end for seg in self.subset_list - ]) - return end_arr - else: - return np.array([]) + return self.subset_list.ends @property def sub_labels(self): - if len(self.subset_list) > 0: - lab_list = [seg.label for seg in self.subset_list] - return lab_list - else: - return [] - + return self.subset_list.labels + def _shift(self, increment): self.start += increment self.end += increment @@ -631,7 +617,7 @@ def fuse_rightwards( fuser.fol = fusee.fol fuser.label = label_fun(fuser.label, fusee.label) - new_list = fuser.subset_list + fusee.subset_list + new_list = fuser._subset_list + fusee.subset_list fuser.set_subset_list(new_list) if fuser.superset_class is Top and fuser.intier: @@ -663,7 +649,7 @@ def fuse_leftwards( fuser.prev = fusee.prev fuser.label = label_fun(fusee.label, fuser.label) - new_list = fusee.subset_list + fuser.subset_list + new_list = fusee.subset_list + fuser._subset_list fuser.set_subset_list(new_list) if fuser.superset_class is Top and fuser.intier: diff --git a/tests/test_sequences/test_sequences.py b/tests/test_sequences/test_sequences.py index 5c662a1..3bc17a0 100644 --- a/tests/test_sequences/test_sequences.py +++ b/tests/test_sequences/test_sequences.py @@ -30,7 +30,7 @@ def test_default_super_instance(self): assert self.seq_int.super_instance is None def test_default_subset_list(self): - assert type(self.seq_int.subset_list) is list + assert isinstance(self.seq_int.subset_list, IntervalList) assert len(self.seq_int.subset_list) == 0 def test_default_sub_starts(self): From c6a2d174de0e0c7ba5a18c04fe1ff6079154f97a Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 13:46:25 -0400 Subject: [PATCH 016/104] move interval list to top --- src/aligned_textgrid/sequences/sequences.py | 226 ++++++++++---------- 1 file changed, 114 insertions(+), 112 deletions(-) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index 53e97e6..296d828 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -20,6 +20,120 @@ else: from typing_extensions import Self +class IntervalList(Sequence): + """A list of SequenceIntervals that + remains sorted + + Args: + *args (SequenceInterval): + SequenceIntervals + + Attributes: + starts (np.array): + An array of start times + ends (np.array): + An array of end times + labels (list[str]): + A list of labels + """ + + def __init__(self, *args): + self._values = [] + self.entry_class = None + for arg in args: + self.append(arg) + + def __getitem__(self, idx): + return self._values[idx] + + def __len__(self): + return len(self._values) + + def __add__(self, other:Sequence): + unique_other_classes = set([x.__class__ for x in other]) + + if len(unique_other_classes) > 1: + raise ValueError("All values in added list must have the same class.") + + if len(unique_other_classes) < 1: + return + + incoming_class = next(iter(unique_other_classes)) + + if not incoming_class is self.entry_class: + raise ValueError("All values in added list must have the same class as original list.") + + return IntervalList(*(self._values + [x for x in other])) + + def __repr__(self): + return self._values.__repr__() + + def _sort(self): + if len(self._values) > 0: + item_starts = np.array([x.start for x in self._values]) + item_order = np.argsort(item_starts) + self._values = [self._values[idx] for idx in item_order] + + + @property + def starts(self)->np.array: + if len(self) > 0: + return np.array([x.start for x in self]) + + return np.array([]) + + @property + def ends(self) -> np.array: + if len(self) > 0: + return np.array([x.end for x in self]) + + return np.array([]) + + @property + def labels(self) -> list[str]: + if len(self) > 0: + return [x.label for x in self] + + return [] + + def append(self, value): + """Append a SequenceInterval to the list. + + After appending, the SequenceIntervals are re-sorted + + Args: + value (SequenceInterval): + A SequenceInterval to append + """ + if self.entry_class is None: + self.entry_class = value.__class__ + + if not self.entry_class is value.__class__: + raise ValueError("All values must have the same class.") + + self._values.append(value) + self._sort() + + def remove(self, x): + """Remove a SequenceInterval from the list + + Args: + x (SequenceInterval): + The SequenceInterval to remove. + """ + self._values.remove(x) + + def pop(self, x): + """Pop a SequneceInterval + + Args: + x (SequenceInterval): + SequenceInterval to pop + """ + if x in self: + pop_idx = self.index(x) + self._values.pop(pop_idx) + class HierarchyMixins: # Ultimately, both of these class variables should be be @@ -259,119 +373,7 @@ class HierarchyPart(HierarchyMixins): def __init__(self): pass -class IntervalList(Sequence): - """A list of SequenceIntervals that - remains sorted - - Args: - *args (SequenceInterval): - SequenceIntervals - Attributes: - starts (np.array): - An array of start times - ends (np.array): - An array of end times - labels (list[str]): - A list of labels - """ - - def __init__(self, *args): - self._values = [] - self.entry_class = None - for arg in args: - self.append(arg) - - def __getitem__(self, idx): - return self._values[idx] - - def __len__(self): - return len(self._values) - - def __add__(self, other:Sequence): - unique_other_classes = set([x.__class__ for x in other]) - - if len(unique_other_classes) > 1: - raise ValueError("All values in added list must have the same class.") - - if len(unique_other_classes) < 1: - return - - incoming_class = next(iter(unique_other_classes)) - - if not incoming_class is self.entry_class: - raise ValueError("All values in added list must have the same class as original list.") - - return IntervalList(*(self._values + [x for x in other])) - - def __repr__(self): - return self._values.__repr__() - - def _sort(self): - if len(self._values) > 0: - item_starts = np.array([x.start for x in self._values]) - item_order = np.argsort(item_starts) - self._values = [self._values[idx] for idx in item_order] - - - @property - def starts(self)->np.array: - if len(self) > 0: - return np.array([x.start for x in self]) - - return np.array([]) - - @property - def ends(self) -> np.array: - if len(self) > 0: - return np.array([x.end for x in self]) - - return np.array([]) - - @property - def labels(self) -> list[str]: - if len(self) > 0: - return [x.label for x in self] - - return [] - - def append(self, value): - """Append a SequenceInterval to the list. - - After appending, the SequenceIntervals are re-sorted - - Args: - value (SequenceInterval): - A SequenceInterval to append - """ - if self.entry_class is None: - self.entry_class = value.__class__ - - if not self.entry_class is value.__class__: - raise ValueError("All values must have the same class.") - - self._values.append(value) - self._sort() - - def remove(self, x): - """Remove a SequenceInterval from the list - - Args: - x (SequenceInterval): - The SequenceInterval to remove. - """ - self._values.remove(x) - - def pop(self, x): - """Pop a SequneceInterval - - Args: - x (SequenceInterval): - SequenceInterval to pop - """ - if x in self: - pop_idx = self.index(x) - self._values.pop(pop_idx) class SequenceInterval(InstanceMixins, InTierMixins, PrecedenceMixins, HierarchyPart): """ From e1feb20ced7459062d68cb26d9d08c65faeb5a82 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 13:54:54 -0400 Subject: [PATCH 017/104] ensuring validation happens --- src/aligned_textgrid/sequences/sequences.py | 63 +++++++++++---------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index 296d828..5a055c7 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -133,7 +133,7 @@ def pop(self, x): if x in self: pop_idx = self.index(x) self._values.pop(pop_idx) - + class HierarchyMixins: # Ultimately, both of these class variables should be be @@ -239,7 +239,7 @@ def set_subset_list(self, subset_list = None): self.append_subset_list(element) self._set_within() self._set_subset_precedence() - + self.validate() else: subset_class_set = set([type(x).__name__ for x in subset_list]) raise Exception(f"The subset_class was defined as {self.subset_class.__name__}, but provided subset_list contained {subset_class_set}") @@ -263,6 +263,7 @@ def append_subset_list(self, subset_instance = None): # avoid recursion if not self is subset_instance.super_instance: subset_instance.set_super_instance(self) + self.validate() elif isinstance(subset_instance, self.subset_class): pass else: @@ -282,6 +283,7 @@ def remove_from_subset_list(self, subset_instance = None): subset_instance.super_instance = None subset_instance.within = None self._set_subset_precedence() + self.validate() def remove_superset(self): """Remove the superset instance from the current subset class @@ -338,36 +340,39 @@ def validate(self) -> bool: validation_concerns = [] if len(self.subset_list) == 0: return True - else: - if not np.allclose(self.start, self.sub_starts[0]): - if self.start < self.sub_starts[0]: - validation_concerns.append( - "First subset interval starts after current interval" - ) - else: - validation_concerns.append( - "First subset interval starts before current interval" - ) - if not np.allclose(self.end, self.sub_ends[-1]): - if self.end > self.sub_ends[-1]: - validation_concerns.append( - "Last subset interval ends before current interval" - ) - else: - validation_concerns.append( - "Last subset interval ends after current interval" - ) - if not np.allclose(self.sub_starts[1:], self.sub_ends[:-1]): + + if self.start is None: + return True + + if not np.allclose(self.start, self.sub_starts[0]): + if self.start < self.sub_starts[0]: validation_concerns.append( - "Not all subintervals fit snugly" + "First subset interval starts after current interval" ) - ## prepping messages - if len(validation_concerns) == 0: - return True else: - validation_warn = "\n".join(validation_concerns) - warnings.warn(validation_warn) - return False + validation_concerns.append( + "First subset interval starts before current interval" + ) + if not np.allclose(self.end, self.sub_ends[-1]): + if self.end > self.sub_ends[-1]: + validation_concerns.append( + "Last subset interval ends before current interval" + ) + else: + validation_concerns.append( + "Last subset interval ends after current interval" + ) + if not np.allclose(self.sub_starts[1:], self.sub_ends[:-1]): + validation_concerns.append( + "Not all subintervals fit snugly" + ) + ## prepping messages + if len(validation_concerns) == 0: + return True + + validation_warn = "\n".join(validation_concerns) + warnings.warn(validation_warn) + return False class HierarchyPart(HierarchyMixins): def __init__(self): From b1fe2a1efa7e180abc701694efce92200ce89c15 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 14:26:45 -0400 Subject: [PATCH 018/104] more interval list work --- src/aligned_textgrid/sequences/sequences.py | 42 +++++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index 5a055c7..cd29638 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -72,8 +72,18 @@ def _sort(self): if len(self._values) > 0: item_starts = np.array([x.start for x in self._values]) item_order = np.argsort(item_starts) - self._values = [self._values[idx] for idx in item_order] - + self._values = [self._values[idx] for idx in item_order] + + def _entry_class_checker(self, value): + if self.entry_class is None: + self.entry_class = value.__class__ + + if not self.entry_class is value.__class__: + raise ValueError("All values must have the same class.") + + def _shift(self, increment): + for value in self: + value._shift(increment) @property def starts(self)->np.array: @@ -96,7 +106,7 @@ def labels(self) -> list[str]: return [] - def append(self, value): + def append(self, value, shift:bool = False): """Append a SequenceInterval to the list. After appending, the SequenceIntervals are re-sorted @@ -105,15 +115,31 @@ def append(self, value): value (SequenceInterval): A SequenceInterval to append """ - if self.entry_class is None: - self.entry_class = value.__class__ - - if not self.entry_class is value.__class__: - raise ValueError("All values must have the same class.") + + self._entry_class_checker(value) + + increment = 0 + if len(self.ends) > 0: + increment = self.ends[-1] + if shift: + value._shift(increment) self._values.append(value) self._sort() + def concat(self, intervals:list|Self): + intervals = IntervalList(*intervals) + + increment = 0 + if len(self.ends) > 0: + increment = self.ends[-1] + + intervals._shift(increment) + + new_values = self + intervals + self._values = new_values + + def remove(self, x): """Remove a SequenceInterval from the list From 8d5adb973045ae69204d46faaaa59a4ebfc5e3c9 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 14:28:28 -0400 Subject: [PATCH 019/104] shift subset list when shifting interval --- src/aligned_textgrid/sequences/sequences.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index cd29638..f603717 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -622,6 +622,8 @@ def sub_labels(self): def _shift(self, increment): self.start += increment self.end += increment + if len(self.subset_list) > 0: + self.subset_list._shift(increment) @property def duration(self) -> float: From de20a384d7e026c0496060c08d03ba428d9ed30c Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 15:40:36 -0400 Subject: [PATCH 020/104] refactor to SequenceList --- src/aligned_textgrid/sequence_list.py | 159 ++++++++++++++++++++ src/aligned_textgrid/sequences/sequences.py | 158 ++----------------- 2 files changed, 169 insertions(+), 148 deletions(-) create mode 100644 src/aligned_textgrid/sequence_list.py diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py new file mode 100644 index 0000000..a3fec82 --- /dev/null +++ b/src/aligned_textgrid/sequence_list.py @@ -0,0 +1,159 @@ +import numpy as np +import warnings +import sys +from collections.abc import Sequence +if sys.version_info >= (3,11): + from typing import Self +else: + from typing_extensions import Self + +class SequenceList(Sequence): + """A list of SequenceIntervals or SequencePoints that + remains sorted + + Args: + *args (SequenceInterval, SequencePoint): + SequenceIntervals or SequencePoints + + Attributes: + starts (np.array): + An array of start times + ends (np.array): + An array of end times + labels (list[str]): + A list of labels + """ + + def __init__(self, *args): + self._values = [] + self.entry_class = None + for arg in args: + self.append(arg) + + def __getitem__(self, idx): + return self._values[idx] + + def __len__(self): + return len(self._values) + + def __add__(self, other:Sequence): + unique_other_classes = set([x.__class__ for x in other]) + + if len(unique_other_classes) > 1: + raise ValueError("All values in added list must have the same class.") + + if len(unique_other_classes) < 1: + return + + incoming_class = next(iter(unique_other_classes)) + + if not incoming_class is self.entry_class: + raise ValueError("All values in added list must have the same class as original list.") + + return SequenceList(*(self._values + [x for x in other])) + + def __repr__(self): + return self._values.__repr__() + + def _sort(self): + if len(self._values) > 0: + if hasattr(self[0], "start"): + item_starts = np.array([x.start for x in self._values]) + if hasattr(self[0], "time"): + item_starts = np.array([x.time for x in self._values]) + item_order = np.argsort(item_starts) + self._values = [self._values[idx] for idx in item_order] + + def _entry_class_checker(self, value): + if self.entry_class is None: + self.entry_class = value.__class__ + + if not self.entry_class is value.__class__: + raise ValueError("All values must have the same class.") + + def _shift(self, increment): + for value in self: + value._shift(increment) + + @property + def starts(self)->np.array: + if len(self) < 1: + return np.array([]) + + if hasattr(self[0], "start"): + return np.array([x.start for x in self]) + + if hasattr(self[0], "time"): + return np.array([x.time for x in self]) + + @property + def ends(self) -> np.array: + if len(self) < 1: + return np.array([]) + + if hasattr(self[0], "end"): + return np.array([x.end for x in self]) + + if hasattr(self[0], "time"): + return np.array([x.time for x in self]) + + @property + def labels(self) -> list[str]: + if len(self) > 0: + return [x.label for x in self] + + return [] + + def append(self, value, shift:bool = False): + """Append a SequenceInterval to the list. + + After appending, the SequenceIntervals are re-sorted + + Args: + value (SequenceInterval): + A SequenceInterval to append + """ + + self._entry_class_checker(value) + + increment = 0 + if len(self.ends) > 0: + increment = self.ends[-1] + if shift: + value._shift(increment) + + self._values.append(value) + self._sort() + + def concat(self, intervals:list|Self): + intervals = SequenceList(*intervals) + + increment = 0 + if len(self.ends) > 0: + increment = self.ends[-1] + + intervals._shift(increment) + + new_values = self + intervals + self._values = new_values + + + def remove(self, x): + """Remove a SequenceInterval from the list + + Args: + x (SequenceInterval): + The SequenceInterval to remove. + """ + self._values.remove(x) + + def pop(self, x): + """Pop a SequneceInterval + + Args: + x (SequenceInterval): + SequenceInterval to pop + """ + if x in self: + pop_idx = self.index(x) + self._values.pop(pop_idx) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index f603717..b2cb26f 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -9,6 +9,7 @@ from praatio.data_classes.interval_tier import IntervalTier from aligned_textgrid.mixins.mixins import InTierMixins, PrecedenceMixins from aligned_textgrid.mixins.within import WithinMixins +from aligned_textgrid.sequence_list import SequenceList from typing import Type, Any import numpy as np import warnings @@ -20,145 +21,6 @@ else: from typing_extensions import Self -class IntervalList(Sequence): - """A list of SequenceIntervals that - remains sorted - - Args: - *args (SequenceInterval): - SequenceIntervals - - Attributes: - starts (np.array): - An array of start times - ends (np.array): - An array of end times - labels (list[str]): - A list of labels - """ - - def __init__(self, *args): - self._values = [] - self.entry_class = None - for arg in args: - self.append(arg) - - def __getitem__(self, idx): - return self._values[idx] - - def __len__(self): - return len(self._values) - - def __add__(self, other:Sequence): - unique_other_classes = set([x.__class__ for x in other]) - - if len(unique_other_classes) > 1: - raise ValueError("All values in added list must have the same class.") - - if len(unique_other_classes) < 1: - return - - incoming_class = next(iter(unique_other_classes)) - - if not incoming_class is self.entry_class: - raise ValueError("All values in added list must have the same class as original list.") - - return IntervalList(*(self._values + [x for x in other])) - - def __repr__(self): - return self._values.__repr__() - - def _sort(self): - if len(self._values) > 0: - item_starts = np.array([x.start for x in self._values]) - item_order = np.argsort(item_starts) - self._values = [self._values[idx] for idx in item_order] - - def _entry_class_checker(self, value): - if self.entry_class is None: - self.entry_class = value.__class__ - - if not self.entry_class is value.__class__: - raise ValueError("All values must have the same class.") - - def _shift(self, increment): - for value in self: - value._shift(increment) - - @property - def starts(self)->np.array: - if len(self) > 0: - return np.array([x.start for x in self]) - - return np.array([]) - - @property - def ends(self) -> np.array: - if len(self) > 0: - return np.array([x.end for x in self]) - - return np.array([]) - - @property - def labels(self) -> list[str]: - if len(self) > 0: - return [x.label for x in self] - - return [] - - def append(self, value, shift:bool = False): - """Append a SequenceInterval to the list. - - After appending, the SequenceIntervals are re-sorted - - Args: - value (SequenceInterval): - A SequenceInterval to append - """ - - self._entry_class_checker(value) - - increment = 0 - if len(self.ends) > 0: - increment = self.ends[-1] - if shift: - value._shift(increment) - - self._values.append(value) - self._sort() - - def concat(self, intervals:list|Self): - intervals = IntervalList(*intervals) - - increment = 0 - if len(self.ends) > 0: - increment = self.ends[-1] - - intervals._shift(increment) - - new_values = self + intervals - self._values = new_values - - - def remove(self, x): - """Remove a SequenceInterval from the list - - Args: - x (SequenceInterval): - The SequenceInterval to remove. - """ - self._values.remove(x) - - def pop(self, x): - """Pop a SequneceInterval - - Args: - x (SequenceInterval): - SequenceInterval to pop - """ - if x in self: - pop_idx = self.index(x) - self._values.pop(pop_idx) class HierarchyMixins: @@ -257,7 +119,7 @@ def set_subset_list(self, subset_list = None): set as the `super_instance` of all objects in the list. """ - self._subset_list = IntervalList() + self._subset_list = SequenceList() if subset_list is None: return if all([isinstance(subint, self.subset_class) for subint in subset_list]): @@ -265,7 +127,7 @@ def set_subset_list(self, subset_list = None): self.append_subset_list(element) self._set_within() self._set_subset_precedence() - self.validate() + #self.validate() else: subset_class_set = set([type(x).__name__ for x in subset_list]) raise Exception(f"The subset_class was defined as {self.subset_class.__name__}, but provided subset_list contained {subset_class_set}") @@ -289,7 +151,7 @@ def append_subset_list(self, subset_instance = None): # avoid recursion if not self is subset_instance.super_instance: subset_instance.set_super_instance(self) - self.validate() + #self.validate() elif isinstance(subset_instance, self.subset_class): pass else: @@ -309,7 +171,7 @@ def remove_from_subset_list(self, subset_instance = None): subset_instance.super_instance = None subset_instance.within = None self._set_subset_precedence() - self.validate() + #self.validate() def remove_superset(self): """Remove the superset instance from the current subset class @@ -510,7 +372,7 @@ def __init__( if self.label != "#": self.set_initial() - self._subset_list = IntervalList() + self._subset_list = SequenceList() self.super_instance= None self.intier = None @@ -587,8 +449,8 @@ def subset_list(self): return self._subset_list @subset_list.setter - def subset_list(self, intervals: list[Self]|IntervalList[Self]): - intervals = IntervalList(*intervals) + def subset_list(self, intervals: list[Self]|SequenceList[Self]): + intervals = SequenceList(*intervals) self.set_subset_list(intervals) @property @@ -661,7 +523,7 @@ def fuse_rightwards( if fuser.intier: fuser.intier.pop(fusee) if fuser.super_instance: - fuser.super_instance.pop(fusee) + fuser.super_instance.subset_list.remove(fusee) else: raise Exception("Cannot fuse rightwards at right edge") @@ -693,7 +555,7 @@ def fuse_leftwards( if fuser.intier: fuser.intier.pop(fusee) if fuser.super_instance: - fuser.super_instance.pop(fusee) + fuser.super_instance.subset_list.remove(fusee) else: raise Exception("Cannot fuse leftwards at right edge") From f135251e4eba40ea43cc0c4d25348385aa1a84f8 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 15:40:56 -0400 Subject: [PATCH 021/104] refactor to sequencelist --- src/aligned_textgrid/sequences/tiers.py | 25 +++++++++++++------------ tests/test_sequences/test_sequences.py | 3 ++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 07ce547..f3485e6 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -6,6 +6,7 @@ from praatio.data_classes.interval_tier import IntervalTier from praatio.data_classes.textgrid import Textgrid from aligned_textgrid.sequences.sequences import SequenceInterval, Top, Bottom +from aligned_textgrid.sequence_list import SequenceList from aligned_textgrid.mixins.tiermixins import TierMixins, TierGroupMixins from aligned_textgrid.mixins.within import WithinMixins import numpy as np @@ -64,7 +65,7 @@ class SequenceTier(Sequence, TierMixins, WithinMixins): """ def __init__( self, - tier: list[Interval] | list[SequenceInterval] | IntervalTier | Self = [], + tier: list[Interval] | list[SequenceInterval] | SequenceList | IntervalTier | Self = SequenceList(), entry_class: Type[SequenceInterval] = None ): @@ -72,7 +73,7 @@ def __init__( if isinstance(tier, IntervalTier): to_check = tier.entries - has_class = any([hasattr(x, "entry_class") for x in to_check]) + has_class = any([hasattr(x, "entry_class") for x in to_check]) if not entry_class and has_class: entry_class = tier[0].entry_class @@ -106,14 +107,15 @@ def __set_classes( self.subset_class = self.entry_class.subset_class def __build_sequence_list(self): - entry_order = np.argsort([x.start for x in self.entry_list]) - self.entry_list = [self.entry_list[idx] for idx in entry_order] - self.sequence_list = [] - for entry in self.entry_list: - this_seq = self.entry_class(entry) - this_seq.set_superset_class(self.superset_class) - this_seq.set_subset_class(self.subset_class) - self.sequence_list += [this_seq] + #entry_order = np.argsort([x.start for x in self.entry_list]) + #self.entry_list = [self.entry_list[idx] for idx in entry_order] + intervals = [self.entry_class(entry) for entry in self.entry_list] + self.sequence_list = SequenceList(*intervals) + # for entry in self.entry_list: + # this_seq = self.entry_class(entry) + # this_seq.set_superset_class(self.superset_class) + # this_seq.set_subset_class(self.subset_class) + # self.sequence_list += [this_seq] def __getitem__(self, idx): return self.sequence_list[idx] @@ -157,8 +159,7 @@ def pop( """ if entry in self.sequence_list: - pop_idx = self.index(entry) - self.sequence_list.pop(pop_idx) + self.sequence_list.remove(entry) if self.superset_class is Top: self.__set_precedence() else: diff --git a/tests/test_sequences/test_sequences.py b/tests/test_sequences/test_sequences.py index 3bc17a0..5dd84b3 100644 --- a/tests/test_sequences/test_sequences.py +++ b/tests/test_sequences/test_sequences.py @@ -1,5 +1,6 @@ import pytest from aligned_textgrid.sequences.sequences import * +from aligned_textgrid.sequence_list import SequenceList from aligned_textgrid.points.points import SequencePoint from aligned_textgrid.sequences.tiers import * import numpy as np @@ -30,7 +31,7 @@ def test_default_super_instance(self): assert self.seq_int.super_instance is None def test_default_subset_list(self): - assert isinstance(self.seq_int.subset_list, IntervalList) + assert isinstance(self.seq_int.subset_list, SequenceList) assert len(self.seq_int.subset_list) == 0 def test_default_sub_starts(self): From be9922641396f901738076d9e5fad5cd17b805e2 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 15:41:04 -0400 Subject: [PATCH 022/104] use sequencelist --- src/aligned_textgrid/points/tiers.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/aligned_textgrid/points/tiers.py b/src/aligned_textgrid/points/tiers.py index 7aef03d..9b9dcfa 100644 --- a/src/aligned_textgrid/points/tiers.py +++ b/src/aligned_textgrid/points/tiers.py @@ -7,6 +7,7 @@ from aligned_textgrid.points.points import SequencePoint from aligned_textgrid.mixins.tiermixins import TierMixins, TierGroupMixins from aligned_textgrid.mixins.within import WithinMixins +from aligned_textgrid.sequence_list import SequenceList import numpy as np from typing import Type from collections.abc import Sequence @@ -65,7 +66,7 @@ class SequencePointTier(Sequence, TierMixins, WithinMixins): """ def __init__( self, - tier:list[Point]|list[SequencePoint]|PointTier|Self = [], + tier:list[Point]|list[SequencePoint]|SequenceList|PointTier|Self = [], entry_class:Type[SequencePoint] = None ): to_check = tier @@ -97,11 +98,11 @@ def __init__( self.name = name entry_order = np.argsort([x.time for x in self.entry_list]) self.entry_list = [self.entry_list[idx] for idx in entry_order] - self.sequence_list = [] - + self.sequence_list = SequenceList() + for entry in self.entry_list: this_point = self.entry_class(entry) - self.sequence_list += [this_point] + self.sequence_list.append(this_point) self.__set_precedence() def __getitem__(self, idx): @@ -135,9 +136,7 @@ def __repr__(self): @property def times(self): - return np.array( - [x.time for x in self.sequence_list] - ) + return self.sequence_list.starts @times.setter def times(self, new_times): @@ -149,19 +148,19 @@ def times(self, new_times): @property def labels(self): - return [x.label for x in self.sequence_list] + return self.sequence_list.labels @property def xmin(self): if len(self.sequence_list) > 0: - return self.sequence_list[0].time + return self.sequence_list.starts.min() else: return None @property def xmax(self): if len(self.sequence_list) > 0: - return self.sequence_list[-1].time + return self.sequence_list.starts.max() else: return None From 0d8bd1cf6865e0c83f24f84546a736d861a55ad0 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 16:22:25 -0400 Subject: [PATCH 023/104] added overlap check fore sequence list --- src/aligned_textgrid/sequence_list.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index a3fec82..19ec537 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -49,8 +49,9 @@ def __add__(self, other:Sequence): if not incoming_class is self.entry_class: raise ValueError("All values in added list must have the same class as original list.") - - return SequenceList(*(self._values + [x for x in other])) + output = SequenceList(*(self._values + [x for x in other])) + output._check_no_overlaps() + return output def __repr__(self): return self._values.__repr__() @@ -75,6 +76,24 @@ def _shift(self, increment): for value in self: value._shift(increment) + def _check_no_overlaps( + self + )->bool: + starts = self.starts + ends = self.ends + + a = np.array([e > starts for e in ends]) + b = np.array([s < ends for s in starts]) + + n_overlaps = (a&b).sum() + + overlaps = n_overlaps > len(self) + + if overlaps: + warnings.warn("Some intervals provided overlap in time") + + return not n_overlaps > len(self) + @property def starts(self)->np.array: if len(self) < 1: From 8e1a2bed2c765274749e47077121168422860bf8 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 16:22:49 -0400 Subject: [PATCH 024/104] convert sequence list to property for setting behavior --- src/aligned_textgrid/sequences/tiers.py | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index f3485e6..11c1921 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -94,6 +94,7 @@ def __init__( self.entry_list = entries self.name = name + self._sequence_list = SequenceList() self.__set_classes(entry_class) self.__build_sequence_list() self.__set_precedence() @@ -107,15 +108,9 @@ def __set_classes( self.subset_class = self.entry_class.subset_class def __build_sequence_list(self): - #entry_order = np.argsort([x.start for x in self.entry_list]) - #self.entry_list = [self.entry_list[idx] for idx in entry_order] intervals = [self.entry_class(entry) for entry in self.entry_list] self.sequence_list = SequenceList(*intervals) - # for entry in self.entry_list: - # this_seq = self.entry_class(entry) - # this_seq.set_superset_class(self.superset_class) - # this_seq.set_subset_class(self.subset_class) - # self.sequence_list += [this_seq] + def __getitem__(self, idx): return self.sequence_list[idx] @@ -125,18 +120,18 @@ def __len__(self): def __set_precedence(self): - for idx,seq in enumerate(self.sequence_list): + for idx,seq in enumerate(self._sequence_list): self.__set_intier(seq) if idx == 0: seq.set_initial() else: - seq.set_prev(self.sequence_list[idx-1]) - if idx == len(self.sequence_list)-1: + seq.set_prev(self._sequence_list[idx-1]) + if idx == len(self._sequence_list)-1: seq.set_final() else: - seq.set_fol(self.sequence_list[idx+1]) + seq.set_fol(self._sequence_list[idx+1]) if issubclass(self.superset_class, Top): - self.contains = self.sequence_list + self.contains = self._sequence_list def __set_intier( self, @@ -167,7 +162,16 @@ def pop( def __repr__(self): return f"Sequence tier of {self.entry_class.__name__}; .superset_class: {self.superset_class.__name__}; .subset_class: {self.subset_class.__name__}" - + + @property + def sequence_list(self): + return self._sequence_list + + @sequence_list.setter + def sequence_list(self, new): + self._sequence_list = SequenceList(*new) + self.__set_precedence() + @property def starts(self): return np.array([x.start for x in self.sequence_list]) From 177efa9d08df59f79d493b5003a2e52ff4542917 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 16:23:07 -0400 Subject: [PATCH 025/104] add and concat for tiers --- src/aligned_textgrid/mixins/tiermixins.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index c92ebaf..01d80e3 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -12,6 +12,28 @@ class TierMixins: last (SequenceInterval): The last entry in the tier. """ + def __add__(self, new): + if self.entry_class.__name__ != new.entry_class.__name__: + raise ValueError("Added tiers must have the same entry class") + + entries = self.sequence_list + new.sequence_list + new_tier = self.__class__(entries) + return new_tier + + + + def concat(self, new): + if self.entry_class.__name__ != new.entry_class.__name__: + raise ValueError("Added tiers must have the same entry class") + + lhs = self.sequence_list + rhs = new.sequence_list + + lhs.concat(rhs) + + self.sequence_list = lhs + + @property def first(self): if hasattr(self, "sequence_list") and len(self.sequence_list) > 0: From b5eb9b9b9a520be9bab723de4b604f6e225fd737 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 17:34:11 -0400 Subject: [PATCH 026/104] make sure cloned classes play nice --- src/aligned_textgrid/sequence_list.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index 19ec537..0aed2be 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -47,9 +47,11 @@ def __add__(self, other:Sequence): incoming_class = next(iter(unique_other_classes)) - if not incoming_class is self.entry_class: + if not incoming_class.__name__ == self.entry_class.__name__: raise ValueError("All values in added list must have the same class as original list.") - output = SequenceList(*(self._values + [x for x in other])) + new_list = self._values + [x for x in other] + new_intervals = [self.entry_class(x) if not x.entry_class is self.entry_class else x for x in new_list] + output = SequenceList(*new_intervals) output._check_no_overlaps() return output From 94986c1046428c36b4f3c34a7c275c83c1cc2be3 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 17:34:42 -0400 Subject: [PATCH 027/104] cleanup methpds to fill gaps --- src/aligned_textgrid/points/tiers.py | 3 ++ src/aligned_textgrid/sequences/tiers.py | 37 +++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/aligned_textgrid/points/tiers.py b/src/aligned_textgrid/points/tiers.py index 9b9dcfa..0e67216 100644 --- a/src/aligned_textgrid/points/tiers.py +++ b/src/aligned_textgrid/points/tiers.py @@ -167,6 +167,9 @@ def xmax(self): def _shift(self, increment): self.times += increment + def cleanup(self): + pass + def get_nearest_point_index( self, time: float diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 11c1921..28581df 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -218,6 +218,25 @@ def xmax(self): else: return None + def cleanup(self): + """ + Insert empty intervals where there are gaps in the existing tier. + """ + existing_intervals = self.sequence_list + for i in range(len(existing_intervals)): + if i+1 == len(existing_intervals): + break + + this_end = existing_intervals[i].end + next_start = existing_intervals[i+1].start + + if np.allclose(this_end, next_start): + continue + + self.sequence_list.append( + self.entry_class((this_end, next_start, "")) + ) + def get_interval_at_time( self, time: float @@ -287,12 +306,24 @@ class TierGroup(Sequence,TierGroupMixins, WithinMixins): """ def __init__( self, - tiers: list[SequenceTier] = [SequenceTier()] + tiers: list[SequenceTier]|Self = [SequenceTier()] ): + name = None + if hasattr(tiers, "name"): + name = tiers.name + + if isinstance(tiers, TierGroup): + tiers = [tier for tier in tiers] + self.tier_list = self._arrange_tiers(tiers) - #self.entry_classes = [x.__class__ for x in self.tier_list] - self._name = self.make_name() + + if name: + self._name = name + else: + self._name = self.make_name() self._set_tier_names() + + #self.entry_classes = [x.__class__ for x in self.tier_list] for idx, tier in enumerate(self.tier_list): if idx == len(self.tier_list)-1: break From 1353324fd5a35b9f1f4780db229c3258a9948375 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 17:35:07 -0400 Subject: [PATCH 028/104] tiergroup add and concat --- src/aligned_textgrid/mixins/tiermixins.py | 41 +++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index 01d80e3..edcddfb 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -2,6 +2,7 @@ from functools import reduce import re import warnings +import numpy as np class TierMixins: """Methods and attributes for Sequence Tiers @@ -33,7 +34,6 @@ def concat(self, new): self.sequence_list = lhs - @property def first(self): if hasattr(self, "sequence_list") and len(self.sequence_list) > 0: @@ -50,7 +50,9 @@ def last(self): if hasattr(self, "sequence_list"): raise IndexError(f"{type(self).__name__} tier with name"\ f" {self.name} has empty sequence_list") - raise AttributeError(f"{type(self).__name__} is not indexable.")\ + raise AttributeError(f"{type(self).__name__} is not indexable.") + + class TierGroupMixins: @@ -60,6 +62,41 @@ class TierGroupMixins: []: Indexable and iterable """ + def _class_check(self, new): + if len(self) != len(new): + raise ValueError("Original TierGroup and new TierGroup must have the same number of tiers.") + + orig_classes = [x.__name__ for x in self.entry_classes] + new_classes = [x.__name__ for x in new.entry_classes] + + for o, n in zip(orig_classes, new_classes): + if o != n: + raise ValueError("Entry classes must be the same between added tier groups") + + def __add__(self, new): + + self._class_check(new) + + new_tiers = [ + t1 + t2 for t1, t2 in zip(self, new) + ] + + new_tg = self.__class__(new_tiers) + new_tg.name = self.name + + return new_tg + + + + def concat(self, new): + self._class_check(new) + + _ = [ + t1.concat(t2) for t1, t2 in zip(self, new) + ] + + + def _set_tier_names(self): entry_class_names = [x.__name__ for x in self.entry_classes] duplicate_names = [ From 16fee06fa808541f03f0c117899e9d9da317f349 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 17 Jun 2024 18:09:55 -0400 Subject: [PATCH 029/104] working atg append --- src/aligned_textgrid/aligned_textgrid.py | 55 +++++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/aligned_textgrid/aligned_textgrid.py b/src/aligned_textgrid/aligned_textgrid.py index f415b07..0e4315d 100644 --- a/src/aligned_textgrid/aligned_textgrid.py +++ b/src/aligned_textgrid/aligned_textgrid.py @@ -61,6 +61,7 @@ def __init__( *, textgrid_path: str = None ): + self.entry_classes = None self.entry_classes = self._reclone_classes(entry_classes) if textgrid_path: textgrid = textgrid_path @@ -168,6 +169,17 @@ def _reclone_classes( unique_classes = list(set(flat_classes)) + orig_classes = [] + orig_class_names = [] + if self.entry_classes: + orig_classes = [c for tg in self.entry_classes for c in tg] + orig_class_names = [c.__name__ for c in orig_classes] + + + already_cloned = [c.__name__ in orig_classes for c in flat_classes] + if all(already_cloned): + return + points = [c for c in unique_classes if issubclass(c, SequencePoint)] tops = [ c @@ -176,8 +188,23 @@ def _reclone_classes( if issubclass(c.superset_class, Top) ] - points_clone = [clone_class(p) for p in points] - tops_clone = [clone_class(t) for t in tops] + points_clone = [] + for p in points: + if p.__name__ in orig_class_names: + points_clone.append( + orig_classes[orig_class_names.index(p.__name__)] + ) + else: + points_clone.append(clone_class(p)) + tops_clone = [] + for t in tops: + if t.__name__ in orig_class_names: + tops_clone.append( + orig_classes[orig_class_names.index(t.__name__)] + ) + else: + tops_clone.append(clone_class(t)) + full_seq_clone = [] for tclone in tops_clone: full_seq_clone += get_class_hierarchy(tclone, []) @@ -346,6 +373,30 @@ def xmax(self): raise ValueError('No maximum time for empty TextGrid.') return np.array([tgroup.xmax for tgroup in self.tier_groups]).max() + def append(self, tier_group:TierGroup): + new_classes = self._reclone_classes(tier_group.entry_classes) + new_tiers = TierGroup( + [ + SequenceTier(t, e) + for t,e in zip(tier_group, new_classes) + ] + ) + + self.tier_groups.append(new_tiers) + self.entry_classes = [[tier.entry_class for tier in tg] for tg in self.tier_groups] + + def cleanup(self): + for tg in self.tier_groups: + tg.cleanup() + + interval_tgs = [tg for tg in self if isinstance(tg, TierGroup)] + tg_starts = np.array([tg.xmin for tg in interval_tgs]) + tg_ends = np.array([tg.xmax for tg in interval_tgs]) + + if np.allclose(tg_starts.min(), tg_starts.max()): + return + + def shift( self, increment: float From 49807cef34ba052a3311615928a5345396210bf6 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 11:57:56 -0400 Subject: [PATCH 030/104] restrict same interval from being added twice --- src/aligned_textgrid/sequence_list.py | 29 +++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index 0aed2be..c54adee 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -36,19 +36,38 @@ def __getitem__(self, idx): def __len__(self): return len(self._values) + def __contains__(self, other): + if other in self._values: + return True + self_tuples = [tuple(x.return_interval()) for x in self] + other_tuple = tuple(other.return_interval()) + if other_tuple in self._values: + return True + + return False + def __add__(self, other:Sequence): + + other = [x for x in other if x not in self] + + if len(other) < 1: + return self + unique_other_classes = set([x.__class__ for x in other]) if len(unique_other_classes) > 1: raise ValueError("All values in added list must have the same class.") if len(unique_other_classes) < 1: - return + return self incoming_class = next(iter(unique_other_classes)) - - if not incoming_class.__name__ == self.entry_class.__name__: + if self.entry_class and not incoming_class.__name__ == self.entry_class.__name__: raise ValueError("All values in added list must have the same class as original list.") + + if not self.entry_class: + self.entry_class = next(iter(unique_other_classes)) + new_list = self._values + [x for x in other] new_intervals = [self.entry_class(x) if not x.entry_class is self.entry_class else x for x in new_list] output = SequenceList(*new_intervals) @@ -134,7 +153,9 @@ def append(self, value, shift:bool = False): value (SequenceInterval): A SequenceInterval to append """ - + if value in self: + return + self._entry_class_checker(value) increment = 0 From c36fe83ed175248083296097512c1138efc01fa0 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 12:10:34 -0400 Subject: [PATCH 031/104] add and append methods for Sequence Intervals --- src/aligned_textgrid/sequences/sequences.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index b2cb26f..72c1132 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -89,6 +89,23 @@ def set_subset_class(cls, subset_class = None): class InstanceMixins(HierarchyMixins, WithinMixins): + + def __add__(self, other): + if issubclass(self.entry_class, other.superset_class): + self.subset_list += [other] + return self + if issubclass(self.entry_class, other.subset_class): + other.subset_list += [self] + return self + + raise ValueError("An added SequenceInterval must either be a subset or superset class of the original.") + + def append(self, other): + if issubclass(self.subset_class, other.entry_class): + self += other + return + raise ValueError("Appended SequenceInterval must be the subset class of the original.") + def set_super_instance(self, super_instance = None): """Sets the specific superset relationship From 5269fa4ad2bb450946c59aa6cead583dee2f5d22 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 13:31:42 -0400 Subject: [PATCH 032/104] match __add__ expectations --- src/aligned_textgrid/sequences/sequences.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index 72c1132..f69a7a2 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -91,18 +91,21 @@ class InstanceMixins(HierarchyMixins, WithinMixins): def __add__(self, other): + + self_copy = self.entry_class(self) + if issubclass(self.entry_class, other.superset_class): - self.subset_list += [other] - return self + self_copy.subset_list += [other] + return self_copy if issubclass(self.entry_class, other.subset_class): - other.subset_list += [self] - return self + other.subset_list += [self_copy] + return self_copy raise ValueError("An added SequenceInterval must either be a subset or superset class of the original.") def append(self, other): if issubclass(self.subset_class, other.entry_class): - self += other + self.subset_list += [other] return raise ValueError("Appended SequenceInterval must be the subset class of the original.") From 41656bd588af28fc85db5cc5e09c105524bd5217 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 13:33:06 -0400 Subject: [PATCH 033/104] add and append tier methods --- src/aligned_textgrid/mixins/tiermixins.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index edcddfb..18ad838 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -14,14 +14,33 @@ class TierMixins: """ def __add__(self, new): - if self.entry_class.__name__ != new.entry_class.__name__: + if not isinstance(new, self.__class__): + msg = f"A {type(self).__name__} can only be added to a {type(self).__name__}." + if isinstance(new, PrecedenceMixins): + msg += " Did you mean to use append()?" + raise ValueError(msg) + if not issubclass(self.entry_class, new.entry_class): raise ValueError("Added tiers must have the same entry class") entries = self.sequence_list + new.sequence_list new_tier = self.__class__(entries) return new_tier - + def append(self, new): + if not isinstance(new, PrecedenceMixins): + msg = "Only SequenceIntervals or SequencePoints can be appended to a tier." + if isinstance(new, TierMixins): + msg += " Did you mean do add (+)?" + raise ValueError(msg) + if not issubclass(self.entry_class, new.entry_class): + raise ValueError("Entry class must match for appended values.") + + entries = self.sequence_list + entries.append(new) + new_tier = self.__class__(entries) + orig_within = self.within + self = new_tier + orig_within.re_relate() def concat(self, new): if self.entry_class.__name__ != new.entry_class.__name__: From 9b363e469ed6ac7a96828363393fa77f8e01c792 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 13:33:41 -0400 Subject: [PATCH 034/104] re_relate method --- src/aligned_textgrid/mixins/tiermixins.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index 18ad838..2d84f12 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -148,6 +148,8 @@ def name(self): def name(self, name): self._name = name + def re_relate(self): + self = self.__class__(self) def get_longest_name_string( self, From 415a0b31eca720400732a4b7ba2ce428225bdadc Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 13:33:58 -0400 Subject: [PATCH 035/104] spacing --- src/aligned_textgrid/mixins/tiermixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index 2d84f12..22782c2 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -147,7 +147,7 @@ def name(self): @name.setter def name(self, name): self._name = name - + def re_relate(self): self = self.__class__(self) From 85c7c7cbb154c359bd500915a72b380b460659c2 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 13:34:25 -0400 Subject: [PATCH 036/104] imports and cleanup --- src/aligned_textgrid/mixins/tiermixins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index 22782c2..7c2073c 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -1,4 +1,5 @@ from difflib import SequenceMatcher +from aligned_textgrid.mixins.mixins import PrecedenceMixins from functools import reduce import re import warnings @@ -43,7 +44,7 @@ def append(self, new): orig_within.re_relate() def concat(self, new): - if self.entry_class.__name__ != new.entry_class.__name__: + if not issubclass(self.entry_class, new.entry_class): raise ValueError("Added tiers must have the same entry class") lhs = self.sequence_list From cf1c66a66cedee1607774b666eebf60efbf8e3ec Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 13:34:42 -0400 Subject: [PATCH 037/104] more principled tier xmin and xmax --- src/aligned_textgrid/sequences/tiers.py | 38 +++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 28581df..8576212 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -207,14 +207,14 @@ def labels(self): @property def xmin(self): if len(self.sequence_list) > 0: - return self.sequence_list[0].start + return self.sequence_list.starts.min() else: return None @property def xmax(self): if len(self.sequence_list) > 0: - return self.sequence_list[-1].end + return self.sequence_list.ends.max() else: return None @@ -433,6 +433,40 @@ def xmin(self): def xmax(self): return np.array([tier.xmax for tier in self.tier_list]).min() + def cleanup(self): + for tier in self: + tier.cleanup() + + + + starts = np.array([tier.xmin for tier in self]) + ends = np.array([tier.xmax for tier in self]) + + if ~np.allclose(starts.ptp(), 0.0): + new_start = self.xmin + + for tier in self: + if np.allclose(tier.xmin, new_start): + continue + + empty_interval = tier.entry_class((new_start, tier.xmin, "")) + tier.append(empty_interval) + + + + if ~np.allclose(ends.ptp(), 0.0): + new_end = self.xmax + + for tier in self: + if np.allclose(tier.xmax, new_end): + continue + + empty_interval = tier.entry_class((tier.xmax, new_end, "")) + tier.append(empty_interval) + + self = TierGroup(self) + + def shift( self, increment: float From 16dc6fd1768cd2e50dbe427bcccc0c6834c3b40e Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 13:37:00 -0400 Subject: [PATCH 038/104] tiers need to be within tier groups for rerelate --- src/aligned_textgrid/mixins/tiermixins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index 7c2073c..3b1c216 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -41,7 +41,8 @@ def append(self, new): new_tier = self.__class__(entries) orig_within = self.within self = new_tier - orig_within.re_relate() + if orig_within: + orig_within.re_relate() def concat(self, new): if not issubclass(self.entry_class, new.entry_class): From 27c8c2e63fb19efe27ce432ada039fbf2dfbb064 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 15:31:09 -0400 Subject: [PATCH 039/104] safer class matching for sequence lists --- src/aligned_textgrid/sequence_list.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index c54adee..0026c08 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -88,9 +88,9 @@ def _sort(self): def _entry_class_checker(self, value): if self.entry_class is None: - self.entry_class = value.__class__ + self.entry_class = value.entry_class - if not self.entry_class is value.__class__: + if not issubclass(self.entry_class, value.entry_class): raise ValueError("All values must have the same class.") def _shift(self, increment): @@ -157,7 +157,8 @@ def append(self, value, shift:bool = False): return self._entry_class_checker(value) - + value = self.entry_class(value) + increment = 0 if len(self.ends) > 0: increment = self.ends[-1] From e8e90fb49096a25c6bbd103e84c45c75783864ab Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 15:31:52 -0400 Subject: [PATCH 040/104] build tiers from sequence lists --- src/aligned_textgrid/sequences/tiers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 8576212..21d1402 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -88,7 +88,7 @@ def __init__( entries = tier.entries name = tier.name if isinstance(tier, SequenceTier): - entries = tier.entry_list + entries = tier.sequence_list if tier.name != tier.entry_class.__name__: name = tier.name From d8a7d80fe75a9cdd74fa4d951895c12ce3df982d Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 15:32:16 -0400 Subject: [PATCH 041/104] re-relate on tier cleanup --- src/aligned_textgrid/sequences/tiers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 21d1402..6d263f5 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -237,6 +237,9 @@ def cleanup(self): self.entry_class((this_end, next_start, "")) ) + if self.within: + self.within.re_relate() + def get_interval_at_time( self, time: float From 29ab996d43607f8851cccb2adaa2546b2a893abd Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 15:32:56 -0400 Subject: [PATCH 042/104] append to subclass tier if in a tier group --- src/aligned_textgrid/sequences/sequences.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index f69a7a2..219e3a6 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -106,6 +106,9 @@ def __add__(self, other): def append(self, other): if issubclass(self.subset_class, other.entry_class): self.subset_list += [other] + if self.intier and self.intier.within: + self_tier_idx = self.intier.within_index + self.intier.within[self_tier_idx+1].append(other) return raise ValueError("Appended SequenceInterval must be the subset class of the original.") From 4e40d1f0153673d016003fe5be1f0b9205ed1686 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 15:35:48 -0400 Subject: [PATCH 043/104] manage tier appending --- src/aligned_textgrid/mixins/tiermixins.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index 3b1c216..d167540 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -38,11 +38,14 @@ def append(self, new): entries = self.sequence_list entries.append(new) - new_tier = self.__class__(entries) orig_within = self.within - self = new_tier + new_tier = self.__class__(entries) + self.__dict__ = new_tier.__dict__ if orig_within: - orig_within.re_relate() + self.within = orig_within + self.within.re_relate() + + def concat(self, new): if not issubclass(self.entry_class, new.entry_class): From 3e4d86ca28e2c7dc5cce5d5ae0beb41b6742c83a Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Tue, 18 Jun 2024 15:36:08 -0400 Subject: [PATCH 044/104] manage re-relation --- src/aligned_textgrid/mixins/tiermixins.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index d167540..28d6e09 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -154,7 +154,26 @@ def name(self, name): self._name = name def re_relate(self): - self = self.__class__(self) + + new_tiers = [ + tier.__class__(tier) + for tier in self + ] + + + + orig_within = self.within + new_tg = self.__class__(new_tiers) + + for old_tier, new_tier in zip(self, new_tg): + for old_seq, new_seq in zip(old_tier, new_tier): + old_seq.__dict__ = new_seq.__dict__ + old_tier.__dict__ = new_tier.__dict__ + self.__dict__ = new_tg.__dict__ + + if orig_within: + self.within = orig_within + def get_longest_name_string( self, From fad540776c098348baa565be04483a0473c00d31 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 11:30:32 -0400 Subject: [PATCH 045/104] new sequence base class --- src/aligned_textgrid/mixins/mixins.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/aligned_textgrid/mixins/mixins.py b/src/aligned_textgrid/mixins/mixins.py index 4021af8..fece623 100644 --- a/src/aligned_textgrid/mixins/mixins.py +++ b/src/aligned_textgrid/mixins/mixins.py @@ -5,6 +5,21 @@ import numpy as np import warnings +class SequenceBaseClass: + + @classmethod + def _cast(cls, obj, re_init = False): + assert isinstance(obj, cls._seq_type), \ + f"A {obj._seq_type.__name__} can only be cast to another {obj._seq_type.__name__}" + + obj.__class__ = cls + + if re_init: + obj.__init__(obj) + + @classmethod + def _set_seq_type(cls, cls2): + cls._seq_type = cls2 class PrecedenceMixins: """Methods and attributes for SequenceIntervals and SequencePoints From 7504caef32df0c00767256863cc1fe422f18acdb Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 11:31:34 -0400 Subject: [PATCH 046/104] update sequences and points with base class --- src/aligned_textgrid/points/points.py | 4 ++-- src/aligned_textgrid/sequences/sequences.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/aligned_textgrid/points/points.py b/src/aligned_textgrid/points/points.py index 38b96d5..8af964b 100644 --- a/src/aligned_textgrid/points/points.py +++ b/src/aligned_textgrid/points/points.py @@ -13,7 +13,7 @@ from typing_extensions import Self -class SequencePoint(PrecedenceMixins, InTierMixins, WithinMixins): +class SequencePoint(SequenceBaseClass, PrecedenceMixins, InTierMixins, WithinMixins): """Sequence Points Args: @@ -209,4 +209,4 @@ def get_interval_at_point( return tier[self.get_interval_index_at_time(tier)] - \ No newline at end of file +SequencePoint._set_seq_type(SequencePoint) \ No newline at end of file diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index 219e3a6..708dc8a 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -291,7 +291,7 @@ def __init__(self): -class SequenceInterval(InstanceMixins, InTierMixins, PrecedenceMixins, HierarchyPart): +class SequenceInterval(SequenceBaseClass, InstanceMixins, InTierMixins, PrecedenceMixins, HierarchyPart): """ A class to describe an interval with precedence relationships and hierarchical relationships @@ -629,3 +629,4 @@ def __init__(self): Bottom.set_subset_class() SequenceInterval.set_superset_class(Top) SequenceInterval.set_subset_class(Bottom) +SequenceInterval._set_seq_type(SequenceInterval) From 90b7c003419b156c53d0d92af1c78a4d01363d7d Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 15:16:32 -0400 Subject: [PATCH 047/104] roll out class casting --- src/aligned_textgrid/mixins/mixins.py | 2 ++ src/aligned_textgrid/mixins/tiermixins.py | 23 ++--------------------- src/aligned_textgrid/points/points.py | 2 +- src/aligned_textgrid/sequence_list.py | 6 ++++-- src/aligned_textgrid/sequences/tiers.py | 2 +- 5 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/aligned_textgrid/mixins/mixins.py b/src/aligned_textgrid/mixins/mixins.py index fece623..5911ad3 100644 --- a/src/aligned_textgrid/mixins/mixins.py +++ b/src/aligned_textgrid/mixins/mixins.py @@ -16,6 +16,8 @@ def _cast(cls, obj, re_init = False): if re_init: obj.__init__(obj) + + return obj @classmethod def _set_seq_type(cls, cls2): diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index 28d6e09..fc85296 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -17,7 +17,7 @@ class TierMixins: def __add__(self, new): if not isinstance(new, self.__class__): msg = f"A {type(self).__name__} can only be added to a {type(self).__name__}." - if isinstance(new, PrecedenceMixins): + if isinstance(new, SequenceBaseClass): msg += " Did you mean to use append()?" raise ValueError(msg) if not issubclass(self.entry_class, new.entry_class): @@ -154,26 +154,7 @@ def name(self, name): self._name = name def re_relate(self): - - new_tiers = [ - tier.__class__(tier) - for tier in self - ] - - - - orig_within = self.within - new_tg = self.__class__(new_tiers) - - for old_tier, new_tier in zip(self, new_tg): - for old_seq, new_seq in zip(old_tier, new_tier): - old_seq.__dict__ = new_seq.__dict__ - old_tier.__dict__ = new_tier.__dict__ - self.__dict__ = new_tg.__dict__ - - if orig_within: - self.within = orig_within - + self.__init__(self) def get_longest_name_string( self, diff --git a/src/aligned_textgrid/points/points.py b/src/aligned_textgrid/points/points.py index 8af964b..22c86bd 100644 --- a/src/aligned_textgrid/points/points.py +++ b/src/aligned_textgrid/points/points.py @@ -1,5 +1,5 @@ from praatio.utilities.constants import Point -from aligned_textgrid.mixins.mixins import PrecedenceMixins, InTierMixins +from aligned_textgrid.mixins.mixins import PrecedenceMixins, InTierMixins, SequenceBaseClass from aligned_textgrid.mixins.within import WithinMixins from aligned_textgrid.sequences.tiers import SequenceTier from aligned_textgrid.sequences.sequences import SequenceInterval diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index 0026c08..a086ac3 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -69,7 +69,7 @@ def __add__(self, other:Sequence): self.entry_class = next(iter(unique_other_classes)) new_list = self._values + [x for x in other] - new_intervals = [self.entry_class(x) if not x.entry_class is self.entry_class else x for x in new_list] + new_intervals = [self.entry_class._cast(x) if not x.entry_class is self.entry_class else x for x in new_list] output = SequenceList(*new_intervals) output._check_no_overlaps() return output @@ -157,7 +157,9 @@ def append(self, value, shift:bool = False): return self._entry_class_checker(value) - value = self.entry_class(value) + value = self.entry_class._cast(value) + if re_init: + value.__init__(value) increment = 0 if len(self.ends) > 0: diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 6d263f5..6a9f77e 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -108,7 +108,7 @@ def __set_classes( self.subset_class = self.entry_class.subset_class def __build_sequence_list(self): - intervals = [self.entry_class(entry) for entry in self.entry_list] + intervals = [self.entry_class._cast(entry) for entry in self.entry_list] self.sequence_list = SequenceList(*intervals) From ccb368de0481093d84f84cc385432203df961dfc Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 15:17:18 -0400 Subject: [PATCH 048/104] sequence list work --- src/aligned_textgrid/sequence_list.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index a086ac3..03557c2 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -48,6 +48,9 @@ def __contains__(self, other): def __add__(self, other:Sequence): + if not isinstance(self, Sequence): + raise ValueError("Only a list, tuple, or SequenceList can be added to SequenceList") + other = [x for x in other if x not in self] if len(other) < 1: @@ -71,6 +74,7 @@ def __add__(self, other:Sequence): new_list = self._values + [x for x in other] new_intervals = [self.entry_class._cast(x) if not x.entry_class is self.entry_class else x for x in new_list] output = SequenceList(*new_intervals) + output._sort() output._check_no_overlaps() return output @@ -90,7 +94,8 @@ def _entry_class_checker(self, value): if self.entry_class is None: self.entry_class = value.entry_class - if not issubclass(self.entry_class, value.entry_class): + if not (issubclass(self.entry_class, value.entry_class) + or issubclass(value.entry_class, self.entry_class)): raise ValueError("All values must have the same class.") def _shift(self, increment): @@ -144,7 +149,7 @@ def labels(self) -> list[str]: return [] - def append(self, value, shift:bool = False): + def append(self, value, shift:bool = False, re_init = False): """Append a SequenceInterval to the list. After appending, the SequenceIntervals are re-sorted @@ -191,6 +196,8 @@ def remove(self, x): The SequenceInterval to remove. """ self._values.remove(x) + if hasattr(x, "super_instance"): + x.remove_superset() def pop(self, x): """Pop a SequneceInterval @@ -202,3 +209,5 @@ def pop(self, x): if x in self: pop_idx = self.index(x) self._values.pop(pop_idx) + if hasattr(self.super_instance): + x.remove_superset() From 532ee10add0df8b10e7007b724d304a55ae6094b Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 15:17:47 -0400 Subject: [PATCH 049/104] mixin work --- src/aligned_textgrid/mixins/tiermixins.py | 37 +++++++++++++++-------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index fc85296..c27efc0 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -1,5 +1,5 @@ from difflib import SequenceMatcher -from aligned_textgrid.mixins.mixins import PrecedenceMixins +from aligned_textgrid.mixins.mixins import SequenceBaseClass from functools import reduce import re import warnings @@ -27,22 +27,35 @@ def __add__(self, new): new_tier = self.__class__(entries) return new_tier - def append(self, new): - if not isinstance(new, PrecedenceMixins): + def append(self, new, re_relate = True): + if not isinstance(new, SequenceBaseClass): msg = "Only SequenceIntervals or SequencePoints can be appended to a tier." if isinstance(new, TierMixins): - msg += " Did you mean do add (+)?" + msg += " Did you mean to add (+)?" raise ValueError(msg) - if not issubclass(self.entry_class, new.entry_class): + if not (issubclass(self.entry_class, new.entry_class) + or issubclass(new.entry_class, self.entry_class)): raise ValueError("Entry class must match for appended values.") + + if new in self: + return - entries = self.sequence_list - entries.append(new) - orig_within = self.within - new_tier = self.__class__(entries) - self.__dict__ = new_tier.__dict__ - if orig_within: - self.within = orig_within + ## triggers precedence resetting + self.sequence_list += [new] + if hasattr(new, "subset_list") \ + and len(new) > 0 \ + and self.within: + down_tier = self.within_index+1 + for interval in new.subset_list: + self.within[down_tier].append(interval, re_relate = False) + + if hasattr(new, "super_instance")\ + and new.super_instance \ + and self.within: + uptier = self.within_index-1 + self.within[uptier].append(new.super_instance, re_relate = False) + + if self.within and re_relate: self.within.re_relate() From ca615ebe320a8bea298f892417fd17d7fca1d7d5 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 15:21:15 -0400 Subject: [PATCH 050/104] SequenceInterval work --- src/aligned_textgrid/sequences/sequences.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index 708dc8a..088cf8d 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -7,7 +7,7 @@ from praatio.utilities.constants import Interval import praatio from praatio.data_classes.interval_tier import IntervalTier -from aligned_textgrid.mixins.mixins import InTierMixins, PrecedenceMixins +from aligned_textgrid.mixins.mixins import InTierMixins, PrecedenceMixins, SequenceBaseClass from aligned_textgrid.mixins.within import WithinMixins from aligned_textgrid.sequence_list import SequenceList from typing import Type, Any @@ -201,9 +201,8 @@ def remove_superset(self): """ if self.super_instance is None: - warnings.warn("Provided SequenceInterval has no superset instance") return - + self.super_instance.remove_from_subset_list(self) @@ -473,8 +472,13 @@ def subset_list(self): @subset_list.setter def subset_list(self, intervals: list[Self]|SequenceList[Self]): + orig_values = self.subset_list intervals = SequenceList(*intervals) self.set_subset_list(intervals) + for orig in orig_values: + + if not orig in self.subset_list: + orig.remove_superset() @property def start(self): From a36d6865a01f24994a68924f68c147569d31d26b Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 15:21:48 -0400 Subject: [PATCH 051/104] tiers work --- src/aligned_textgrid/sequences/tiers.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 6a9f77e..fe9e6f2 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -232,10 +232,11 @@ def cleanup(self): if np.allclose(this_end, next_start): continue - - self.sequence_list.append( + + ## triggers precedence resetting + self.sequence_list += [ self.entry_class((this_end, next_start, "")) - ) + ] if self.within: self.within.re_relate() @@ -326,6 +327,11 @@ def __init__( self._name = self.make_name() self._set_tier_names() + for tier in tiers: + for entry in tier: + if hasattr(entry, "super_instance"): + + entry.remove_superset() #self.entry_classes = [x.__class__ for x in self.tier_list] for idx, tier in enumerate(self.tier_list): if idx == len(self.tier_list)-1: From 379ad1a9a12a1e5870ab0f236df9eb645517f82e Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:32:41 -0400 Subject: [PATCH 052/104] return praatio method --- src/aligned_textgrid/mixins/mixins.py | 7 +++++++ src/aligned_textgrid/sequence_list.py | 10 +++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/aligned_textgrid/mixins/mixins.py b/src/aligned_textgrid/mixins/mixins.py index 5911ad3..aee7966 100644 --- a/src/aligned_textgrid/mixins/mixins.py +++ b/src/aligned_textgrid/mixins/mixins.py @@ -163,6 +163,13 @@ def get_tierwise( return self.intier[self.tier_index + idx] else: return None + + def return_praatio(self): + if "Interval" in self._seq_type.__name__: + return self.return_interval() + + if "Point" in self._seq_type.__name__: + return self.return_point() def return_interval(self) -> Interval: """Return current object as `Interval` diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index 03557c2..d600e2a 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -37,11 +37,15 @@ def __len__(self): return len(self._values) def __contains__(self, other): + if len(self) < 1: + return False + if other in self._values: return True - self_tuples = [tuple(x.return_interval()) for x in self] - other_tuple = tuple(other.return_interval()) - if other_tuple in self._values: + + self_tuples = [tuple(x.return_praatio()) for x in self] + other_tuple = tuple(other.return_praatio()) + if other_tuple in self_tuples: return True return False From a206b9ea5c3e961a5a6d640b36e3007554a47673 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:33:01 -0400 Subject: [PATCH 053/104] typo fix on pop --- src/aligned_textgrid/sequence_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index d600e2a..67258ca 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -213,5 +213,5 @@ def pop(self, x): if x in self: pop_idx = self.index(x) self._values.pop(pop_idx) - if hasattr(self.super_instance): + if hasattr(x, "super_instance"): x.remove_superset() From 552cf08cd325bc3b6d3c5e2fac8fb35789e9c160 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:33:38 -0400 Subject: [PATCH 054/104] safer set fol and set initial --- src/aligned_textgrid/mixins/mixins.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aligned_textgrid/mixins/mixins.py b/src/aligned_textgrid/mixins/mixins.py index aee7966..c1a5b95 100644 --- a/src/aligned_textgrid/mixins/mixins.py +++ b/src/aligned_textgrid/mixins/mixins.py @@ -99,9 +99,9 @@ def set_final(self): instance does not appear in `self.super_instance.subset_list` """ - if hasattr(self, "start"): + if "Interval" in self._seq_type.__name__: self.set_fol(type(self)(Interval(None, None, "#"))) - elif hasattr(self, "time"): + elif "Point" in self._seq_type.__name__: self.set_fol(type(self)(Point(None, "#"))) def set_initial(self): @@ -110,9 +110,9 @@ def set_initial(self): While `self.prev` is defined for these entries, the actual instance does not appear in `self.super_instance.subset_list` """ - if hasattr(self, "start"): + if "Interval" in self._seq_type.__name__: self.set_prev(type(self)(Interval(None, None, "#"))) - elif hasattr(self, "time"): + elif "Point" in self._seq_type.__name__: self.set_prev(type(self)(Point(None, "#"))) class InTierMixins: From 4b10af9e00446038a880f16d9980c52176b0133a Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:34:12 -0400 Subject: [PATCH 055/104] safer cast --- src/aligned_textgrid/mixins/mixins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aligned_textgrid/mixins/mixins.py b/src/aligned_textgrid/mixins/mixins.py index c1a5b95..d42d710 100644 --- a/src/aligned_textgrid/mixins/mixins.py +++ b/src/aligned_textgrid/mixins/mixins.py @@ -9,6 +9,9 @@ class SequenceBaseClass: @classmethod def _cast(cls, obj, re_init = False): + if not isinstance(obj, SequenceBaseClass): + obj = cls(obj) + return obj assert isinstance(obj, cls._seq_type), \ f"A {obj._seq_type.__name__} can only be cast to another {obj._seq_type.__name__}" From 5a483e2d07ba97005da942455c37870e2eb7012e Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:34:45 -0400 Subject: [PATCH 056/104] fixing point init --- src/aligned_textgrid/points/points.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/aligned_textgrid/points/points.py b/src/aligned_textgrid/points/points.py index 22c86bd..d51c693 100644 --- a/src/aligned_textgrid/points/points.py +++ b/src/aligned_textgrid/points/points.py @@ -64,11 +64,11 @@ class SequencePoint(SequenceBaseClass, PrecedenceMixins, InTierMixins, WithinMi def __init__( self, - point: list|tuple|Point|Self = (0, "") + point: list|tuple|Point|Self = (None, None) ): - + self._seq_type = SequencePoint if isinstance(point, SequencePoint): - point = (point.time, point.label) + point = Point(point.time, point.label) if len(point) > 2: raise ValueError(( @@ -76,8 +76,10 @@ def __init__( "no more than 2 values long. " f"{len(point)} were provided." )) + point = Point(*point) + self.time = point.time self.label = point.label From cc0ccf70994c37fecf7f3b0f42dbc30b97912c42 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:35:06 -0400 Subject: [PATCH 057/104] dummy start for points for sequencelist compatibility --- src/aligned_textgrid/points/points.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/aligned_textgrid/points/points.py b/src/aligned_textgrid/points/points.py index d51c693..f90e07a 100644 --- a/src/aligned_textgrid/points/points.py +++ b/src/aligned_textgrid/points/points.py @@ -123,6 +123,10 @@ def time(self): def time(self, time): self._time = time + @property + def start(self): + return self.time + @property def fol_distance(self): if self.fol and self.fol.time: From 51253e424c48e2fd44ac02331dfd1db26fbdc32a Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:35:23 -0400 Subject: [PATCH 058/104] use cast in point tier --- src/aligned_textgrid/points/tiers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aligned_textgrid/points/tiers.py b/src/aligned_textgrid/points/tiers.py index 0e67216..b56902b 100644 --- a/src/aligned_textgrid/points/tiers.py +++ b/src/aligned_textgrid/points/tiers.py @@ -101,7 +101,7 @@ def __init__( self.sequence_list = SequenceList() for entry in self.entry_list: - this_point = self.entry_class(entry) + this_point = self.entry_class._cast(entry) self.sequence_list.append(this_point) self.__set_precedence() From 330c2830f2903f3d86808c788047859e19ed13f0 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:35:46 -0400 Subject: [PATCH 059/104] cleanup sequence init --- src/aligned_textgrid/sequences/sequences.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index 088cf8d..366e82e 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -360,6 +360,7 @@ def __init__( *, Interval = None ): + self._seq_type = SequenceInterval if Interval: interval = Interval @@ -369,8 +370,6 @@ def __init__( interval.end, interval.label ) - rep_none = 3 - len(interval) - interval += tuple([None]) * rep_none if len(interval) > 3: raise ValueError(( From 7952aebead2de311117d2858c8e6b26c0da253ae Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:36:14 -0400 Subject: [PATCH 060/104] fusion fix --- src/aligned_textgrid/sequences/sequences.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index 366e82e..d46d2b5 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -541,14 +541,14 @@ def fuse_rightwards( fuser.label = label_fun(fuser.label, fusee.label) new_list = fuser._subset_list + fusee.subset_list - fuser.set_subset_list(new_list) - + fuser.subset_list = new_list + if fuser.superset_class is Top and fuser.intier: fuser.intier.pop(fusee) else: if fuser.intier: fuser.intier.pop(fusee) - if fuser.super_instance: + if fuser.super_instance and fusee in fuser.super_instance: fuser.super_instance.subset_list.remove(fusee) else: raise Exception("Cannot fuse rightwards at right edge") @@ -565,7 +565,6 @@ def fuse_leftwards( fusee = self.prev fuser = self - if not fusee.label == "#": fuser.start = fusee.start @@ -573,14 +572,14 @@ def fuse_leftwards( fuser.label = label_fun(fusee.label, fuser.label) new_list = fusee.subset_list + fuser._subset_list - fuser.set_subset_list(new_list) - + + fuser.subset_list = new_list if fuser.superset_class is Top and fuser.intier: fuser.intier.pop(fusee) else: if fuser.intier: fuser.intier.pop(fusee) - if fuser.super_instance: + if fuser.super_instance and fusee in fuser.super_instance: fuser.super_instance.subset_list.remove(fusee) else: raise Exception("Cannot fuse leftwards at right edge") From 105755a9b6683bdc041ccf6c3622bcbc2b5333a2 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:36:40 -0400 Subject: [PATCH 061/104] no need to set point start --- src/aligned_textgrid/outputs/to_dataframe.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/aligned_textgrid/outputs/to_dataframe.py b/src/aligned_textgrid/outputs/to_dataframe.py index 7294df5..a678a94 100644 --- a/src/aligned_textgrid/outputs/to_dataframe.py +++ b/src/aligned_textgrid/outputs/to_dataframe.py @@ -25,7 +25,6 @@ def sequence_to_df( class_name = type(obj).__name__ if isinstance(obj, SequencePoint): - obj.start = obj.time attributes_to_get.pop( attributes_to_get.index("end") ) From fb0546bf8deea00c02c12b63ca783e6cff03e6cb Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:36:54 -0400 Subject: [PATCH 062/104] changed default point --- tests/test_sequences/test_points.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_sequences/test_points.py b/tests/test_sequences/test_points.py index 5f43174..6b3bf23 100644 --- a/tests/test_sequences/test_points.py +++ b/tests/test_sequences/test_points.py @@ -13,10 +13,10 @@ def test_default_class(self): assert self.seq_point.__class__ is SequencePoint def test_default_time(self): - assert self.seq_point.time == 0 + assert self.seq_point.time is None def test_default_label(self): - assert self.seq_point.label == "" + assert self.seq_point.label is None def test_default_fol(self): assert self.seq_point.fol.label == "#" From ec0e44232f98c20246c345e2a72700563d67466c Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:37:15 -0400 Subject: [PATCH 063/104] fixed duplicate interval --- tests/test_sequences/test_sequences.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_sequences/test_sequences.py b/tests/test_sequences/test_sequences.py index 5dd84b3..f80fe16 100644 --- a/tests/test_sequences/test_sequences.py +++ b/tests/test_sequences/test_sequences.py @@ -402,8 +402,8 @@ def test_first_last_errors(self): def test_subset_pop(self): upper1 = self.UpperClass(Interval(0,10,"upper")) lower1 = self.LowerClass(Interval(0,5,"lower1")) - lower2 = self.LowerClass(Interval(5,10,"lower2")) - lower3 = self.LowerClass(Interval(5,10,"lower2")) + lower2 = self.LowerClass(Interval(5,8,"lower2")) + lower3 = self.LowerClass(Interval(8,10,"lower3")) upper1.set_subset_list([lower1, lower2, lower3]) From 356548418e842f45855654dcf6f78bee73d54254 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 16:37:34 -0400 Subject: [PATCH 064/104] New contains checks on tuples --- tests/test_tiers/test_PointTier.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_tiers/test_PointTier.py b/tests/test_tiers/test_PointTier.py index 0a1e02b..2b978cd 100644 --- a/tests/test_tiers/test_PointTier.py +++ b/tests/test_tiers/test_PointTier.py @@ -40,7 +40,6 @@ def test_indexing(self): def test_tier_contains(self): assert self.seq_point_a in self.seq_point_tier assert self.seq_point_a2 in self.seq_point_tier2 - assert not self.point_a2 in self.seq_point_tier2 def test_intier(self): assert self.seq_point_a.intier is self.seq_point_tier From a1a0180e2e467522bd919cfef47be6f5db011684 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 18:38:03 -0400 Subject: [PATCH 065/104] better get index at time --- src/aligned_textgrid/sequences/tiers.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index fe9e6f2..67f16e8 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -253,10 +253,16 @@ def get_interval_at_time( Returns: (int): Index of the interval """ - out_idx = np.searchsorted(self.starts, time, side = "left") - 1 - if np.allclose(self.starts[out_idx+1], time): - out_idx = out_idx+1 - return out_idx + out_idx = np.argwhere( + np.logical_and( + self.starts <= time, + self.ends >= time + ) + ) + if out_idx.size >= 1: + return int(out_idx.squeeze()) + else: + return None def return_tier(self) -> IntervalTier: """Returns a `praatio` interval tier From b4cb66eb28c763ce4fe6d970ca01afea56deac17 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 18:38:12 -0400 Subject: [PATCH 066/104] sequece cleanup --- src/aligned_textgrid/sequences/sequences.py | 36 ++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index d46d2b5..5a9d601 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -520,7 +520,41 @@ def duration(self) -> float: @property def entry_class(self): return self.__class__ - + + def cleanup(self): + if isinstance(self.subset_class, Bottom): + return + if not len(self.subset_list) > 0: + self.append( + self.subset_class(( + self.start, + self.end, + "" + )) + ) + return + + to_add = SequenceList() + + if not np.allclose(self.start, self.sub_starts[0]): + new_interval = self.subset_class((self.start, self.sub_starts[0], "")) + to_add += [new_interval] + + if not np.allclose(self.end, self.sub_ends[-1]): + new_interval = self.subset_class((self.sub_ends[-1], self.end, "")) + to_add += [new_interval] + + for idx, interval in enumerate(self.subset_list): + if idx + 1 == len(self): + break + + if not np.allclose(interval.end, self.subset_list[idx+1].start): + new_interval = self.subset_class((interval.end,self.subset_list[idx+1].start, "")) + to_add += [new_interval] + + for interval in to_add: + self.append(interval) + ## Fusion def fuse_rightwards( self, From c09bbca8dd4766c994b7395bc67bec59d664b6cb Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 18:38:34 -0400 Subject: [PATCH 067/104] tiergroup cleanup --- src/aligned_textgrid/sequences/tiers.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 67f16e8..e7d0e59 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -448,6 +448,23 @@ def xmin(self): def xmax(self): return np.array([tier.xmax for tier in self.tier_list]).min() + def project_up(self, interval:SequenceInterval): + if issubclass(interval.superset_class, Top): + return + up_index = interval.intier.within_index - 1 + up_tier:SequenceTier = interval.intier.within[up_index] + midp = interval.start + (interval.duration/2) + print("midp", midp) + if not up_tier.get_interval_at_time(midp) is None: + return + new_interval = up_tier.entry_class(( + interval.start, + interval.end, + "" + )) + + up_tier.append(new_interval) + def cleanup(self): for tier in self: tier.cleanup() From 0fd98edafc9afdcb376467e3d6d65197c258102e Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 18:38:52 -0400 Subject: [PATCH 068/104] tiergroup cleanup --- src/aligned_textgrid/sequences/tiers.py | 40 ++++++++----------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index e7d0e59..b25b098 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -466,37 +466,21 @@ def project_up(self, interval:SequenceInterval): up_tier.append(new_interval) def cleanup(self): - for tier in self: - tier.cleanup() - - - - starts = np.array([tier.xmin for tier in self]) - ends = np.array([tier.xmax for tier in self]) - - if ~np.allclose(starts.ptp(), 0.0): - new_start = self.xmin - - for tier in self: - if np.allclose(tier.xmin, new_start): - continue - - empty_interval = tier.entry_class((new_start, tier.xmin, "")) - tier.append(empty_interval) - + for idx, tier in enumerate(self): + if issubclass(tier.subset_class, Bottom): + break + for interval in tier: + interval.cleanup() - if ~np.allclose(ends.ptp(), 0.0): - new_end = self.xmax - - for tier in self: - if np.allclose(tier.xmax, new_end): - continue - - empty_interval = tier.entry_class((tier.xmax, new_end, "")) - tier.append(empty_interval) + for idx, tier in enumerate(reversed(self)): + for interval in tier: + self.project_up(interval) - self = TierGroup(self) + for tier in self: + tier.cleanup() + + self.re_relate() def shift( From d617172a7aadbcb98fabde1fa15493495579c266 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 18:46:35 -0400 Subject: [PATCH 069/104] drop print statement --- src/aligned_textgrid/sequences/tiers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index b25b098..0f48697 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -454,7 +454,6 @@ def project_up(self, interval:SequenceInterval): up_index = interval.intier.within_index - 1 up_tier:SequenceTier = interval.intier.within[up_index] midp = interval.start + (interval.duration/2) - print("midp", midp) if not up_tier.get_interval_at_time(midp) is None: return new_interval = up_tier.entry_class(( From 50bcaffc8bb7d885433789e819bf04894e9680f4 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 18:47:44 -0400 Subject: [PATCH 070/104] atg cleanup --- src/aligned_textgrid/aligned_textgrid.py | 33 +++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/aligned_textgrid/aligned_textgrid.py b/src/aligned_textgrid/aligned_textgrid.py index 0e4315d..bb5732b 100644 --- a/src/aligned_textgrid/aligned_textgrid.py +++ b/src/aligned_textgrid/aligned_textgrid.py @@ -381,7 +381,7 @@ def append(self, tier_group:TierGroup): for t,e in zip(tier_group, new_classes) ] ) - + self.tier_groups.append(new_tiers) self.entry_classes = [[tier.entry_class for tier in tg] for tg in self.tier_groups] @@ -393,10 +393,37 @@ def cleanup(self): tg_starts = np.array([tg.xmin for tg in interval_tgs]) tg_ends = np.array([tg.xmax for tg in interval_tgs]) - if np.allclose(tg_starts.min(), tg_starts.max()): + if np.allclose(tg_starts.min(), tg_starts.max()) and \ + np.allclose(tg_ends.min(), tg_ends.max()): return - + + for tg in self.tier_groups: + if np.allclose(tg.xmin, tg_starts.min()): + continue + + start = tg_starts.min() + end = tg.xmin + + tg_classes = tg.entry_classes + + empty_intervals = [c((start, end, "")) for c in tg_classes] + for tier, interval in zip(tg, empty_intervals): + tier.append(interval) + + for tg in self.tier_groups: + if np.allclose(tg.xmax, tg_starts.max()): + continue + + start = tg.xmax + end = tg_starts.max() + + tg_classes = tg.entry_classes + + empty_intervals = [c((start, end, "")) for c in tg_classes] + for tier, interval in zip(tg, empty_intervals): + tier.append(interval) + def shift( self, increment: float From f198ec184bda467d4a6048cbbdd95056f31fc1c1 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 18:51:49 -0400 Subject: [PATCH 071/104] fix get interval at time --- src/aligned_textgrid/sequences/tiers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 0f48697..73a8a6f 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -256,7 +256,7 @@ def get_interval_at_time( out_idx = np.argwhere( np.logical_and( self.starts <= time, - self.ends >= time + self.ends > time ) ) if out_idx.size >= 1: From e09bf40af30abec3184e7d22b21ebae9ef2a2cec Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 19:54:21 -0400 Subject: [PATCH 072/104] more exports --- src/aligned_textgrid/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/aligned_textgrid/__init__.py b/src/aligned_textgrid/__init__.py index fd08011..b1d4222 100644 --- a/src/aligned_textgrid/__init__.py +++ b/src/aligned_textgrid/__init__.py @@ -1,20 +1,24 @@ from aligned_textgrid.sequences.sequences import SequenceInterval, Top, Bottom from aligned_textgrid.sequences.word_and_phone import Word, Phone from aligned_textgrid.points.points import SequencePoint -from aligned_textgrid.points.tiers import SequencePointTier -from aligned_textgrid.sequences.tiers import SequenceTier +from aligned_textgrid.points.tiers import SequencePointTier, PointsGroup +from aligned_textgrid.sequences.tiers import SequenceTier, TierGroup from aligned_textgrid.aligned_textgrid import AlignedTextGrid +from aligned_textgrid.sequence_list import SequenceList from aligned_textgrid.custom_classes import custom_classes from aligned_textgrid.outputs.to_dataframe import to_df __all__ = [ "SequenceInterval", "SequencePoint", + "PointsGroup", "Top", "Bottom", "Word", "Phone", "SequenceTier", "SequencePointTier", + "TierGroup", + "SequenceList", "AlignedTextGrid", "custom_classes", "to_df" From ed3118cb74ee47f1e395e8ff8718621f74f5b0ed Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Thu, 20 Jun 2024 19:54:42 -0400 Subject: [PATCH 073/104] sequence list tests --- src/aligned_textgrid/sequence_list.py | 9 +- src/aligned_textgrid/sequences/sequences.py | 8 +- tests/test_sequence_list.py | 146 ++++++++++++++++++++ 3 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 tests/test_sequence_list.py diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index 67258ca..0477695 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -128,12 +128,12 @@ def _check_no_overlaps( def starts(self)->np.array: if len(self) < 1: return np.array([]) - + if hasattr(self[0], "time"): + return np.array([x.time for x in self]) if hasattr(self[0], "start"): return np.array([x.start for x in self]) - if hasattr(self[0], "time"): - return np.array([x.time for x in self]) + @property def ends(self) -> np.array: @@ -204,7 +204,7 @@ def remove(self, x): x.remove_superset() def pop(self, x): - """Pop a SequneceInterval + """Pop a SequenceInterval Args: x (SequenceInterval): @@ -214,4 +214,5 @@ def pop(self, x): pop_idx = self.index(x) self._values.pop(pop_idx) if hasattr(x, "super_instance"): + print("removing") x.remove_superset() diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index 5a9d601..ec3f5fc 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -202,8 +202,12 @@ def remove_superset(self): if self.super_instance is None: return + if self in self.super_instance.subset_list: + self.super_instance.remove_from_subset_list(self) + + self.super_instance = None + self.within = None - self.super_instance.remove_from_subset_list(self) def _set_subset_precedence(self): @@ -443,7 +447,7 @@ def pop( Args: subset_instance (SequenceInterval): A sequence interval to pop """ - self.subset_list.pop(subset_instance) + self._subset_list.pop(subset_instance) self._set_subset_precedence() def __repr__(self) -> str: diff --git a/tests/test_sequence_list.py b/tests/test_sequence_list.py new file mode 100644 index 0000000..3c273bd --- /dev/null +++ b/tests/test_sequence_list.py @@ -0,0 +1,146 @@ +from aligned_textgrid import SequenceList, SequenceInterval, SequencePoint, custom_classes +import numpy as np +import pytest + +def make_sequences(cls, n): + starts, step = np.linspace(0, 20, num = n, retstep=True) + ends = starts + step + + labels = [f"lab_{x}" for x in np.arange(n)] + + out_list = [] + for start, end, label in zip(starts, ends, labels): + if issubclass(cls, SequenceInterval): + out_list += [cls((start, end, label))] + + if issubclass(cls, SequencePoint): + out_list += [cls((start, label))] + return out_list + + + +class TestSequenceList: + def test_interval_list(self): + MyInterval, = custom_classes(["MyInterval"]) + my_list = SequenceList( + MyInterval((2,4, "a")) + ) + + assert issubclass(my_list.entry_class, MyInterval) + + new_interval = MyInterval((0, 1, "b")) + my_list.append(new_interval) + + assert new_interval in my_list + + # check the result is properly sorted + assert my_list.index(new_interval) == 0 + + next_interval = MyInterval((5, 10, "c")) + + my_list += [next_interval] + + assert new_interval in my_list + assert next_interval in my_list + + def test_point_list(self): + MyPoint, = custom_classes(["MyPoint"], points = [0]) + + my_list = SequenceList(MyPoint((5, "a"))) + assert issubclass(my_list.entry_class, MyPoint) + + new_point = MyPoint((0, "b")) + my_list.append(new_point) + + assert new_point in my_list + assert my_list.index(new_point) == 0 + + next_point = MyPoint((10, "c")) + + my_list += [next_point] + + assert new_point in my_list + assert next_point in my_list + + def test_strictnes(self): + MyWord, MyPhone, MyPoint = custom_classes(["MyWord", "MyPhone", "MyPoint"], points=[2]) + + word_list = SequenceList(*make_sequences(MyWord, 20)) + mix_list = make_sequences(MyWord, 5) + make_sequences(MyPhone, 5) + + with pytest.raises(ValueError): + word_list.append(MyPhone((0,3,"x"))) + + with pytest.raises(ValueError): + word_list += [MyPhone((0,3, "x"))] + + with pytest.raises(ValueError): + word_list.append(MyPoint((0,"x"))) + + with pytest.raises(ValueError): + new_list = SequenceList() + new_list += mix_list + + def test_properties(self): + MyWord, MyPoint = custom_classes(["MyWord", "MyPoint"], points=[1]) + n = 10 + + my_list = SequenceList() + my_point_list = SequenceList() + assert len(my_list.labels) == 0 + assert len(my_list.starts) == 0 + assert len(my_list.ends) == 0 + + word_list = make_sequences(MyWord, n) + point_list = make_sequences(MyPoint, n) + + my_list += word_list + my_point_list += point_list + + assert len(my_list.labels) == n + assert len(my_list.starts) == n + assert len(my_list.ends) == n + + assert len(my_point_list.labels) == n + assert len(my_point_list.starts) == n + assert len(my_point_list.ends) == n + + + def test_pop_remove(self): + MyWord, = custom_classes(["MyWord"]) + word_list = make_sequences(MyWord, 20) + first_word = word_list[0] + second_word = word_list[1] + + my_list = SequenceList(*word_list) + + assert first_word in my_list + assert second_word in my_list + + my_list.pop(first_word) + assert not first_word in my_list + + my_list.remove(second_word) + assert not second_word in my_list + + def test_remove_super_instance(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + + word = MyWord((0,5, "a")) + phones = [ + MyPhone((0,2,"A")), + MyPhone((2,5, "AA")) + ] + + for p in phones: + word.append(p) + + assert phones[0] in word + assert phones[0].super_instance is word + + word.subset_list.remove(phones[0]) + + assert not phones[0] in word + assert not phones[0].super_instance is word + + pass From 6e76d5c0da7ade10681471772c32ea71608e924e Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 10:49:30 -0400 Subject: [PATCH 074/104] SequenceList tests --- tests/test_sequence_list.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/tests/test_sequence_list.py b/tests/test_sequence_list.py index 3c273bd..a0236f7 100644 --- a/tests/test_sequence_list.py +++ b/tests/test_sequence_list.py @@ -2,7 +2,7 @@ import numpy as np import pytest -def make_sequences(cls, n): +def make_sequences(cls, n)->list[SequenceInterval]: starts, step = np.linspace(0, 20, num = n, retstep=True) ends = starts + step @@ -17,8 +17,6 @@ def make_sequences(cls, n): out_list += [cls((start, label))] return out_list - - class TestSequenceList: def test_interval_list(self): MyInterval, = custom_classes(["MyInterval"]) @@ -142,5 +140,25 @@ def test_remove_super_instance(self): assert not phones[0] in word assert not phones[0].super_instance is word - + + def test_concat(self): + MyWord, = custom_classes(["MyWord"]) + + n = 5 + + words1 = make_sequences(MyWord, n) + words2 = make_sequences(MyWord, n) + + my_list1 = SequenceList(*words1) + my_list2 = SequenceList(*words2) + + orig_end = my_list1.ends.max() + + my_list1.concat(my_list2) + + assert len(my_list1) == (n*2) + assert my_list1.ends.max() == orig_end + orig_end + + + pass From 1df43100b28529df7bd393836a79a06256f05665 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 11:33:41 -0400 Subject: [PATCH 075/104] More typing for SequenceList --- src/aligned_textgrid/sequence_list.py | 30 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index 0477695..0f1d209 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -7,6 +7,12 @@ else: from typing_extensions import Self +from typing import TYPE_CHECKING, TypeVar +if TYPE_CHECKING: + from aligned_textgrid import SequenceInterval, SequencePoint + +SeqVar = TypeVar("SeqVar", 'SequenceInterval', 'SequencePoint') + class SequenceList(Sequence): """A list of SequenceIntervals or SequencePoints that remains sorted @@ -24,19 +30,19 @@ class SequenceList(Sequence): A list of labels """ - def __init__(self, *args): + def __init__(self, *args:SeqVar): self._values = [] self.entry_class = None for arg in args: self.append(arg) - def __getitem__(self, idx): + def __getitem__(self:Sequence[SeqVar], idx:int)->SeqVar: return self._values[idx] - def __len__(self): + def __len__(self)->int: return len(self._values) - def __contains__(self, other): + def __contains__(self:Sequence[SeqVar], other:SeqVar)->bool: if len(self) < 1: return False @@ -50,7 +56,7 @@ def __contains__(self, other): return False - def __add__(self, other:Sequence): + def __add__(self, other:Sequence[SeqVar]) -> Self: if not isinstance(self, Sequence): raise ValueError("Only a list, tuple, or SequenceList can be added to SequenceList") @@ -85,7 +91,7 @@ def __add__(self, other:Sequence): def __repr__(self): return self._values.__repr__() - def _sort(self): + def _sort(self)->None: if len(self._values) > 0: if hasattr(self[0], "start"): item_starts = np.array([x.start for x in self._values]) @@ -94,7 +100,7 @@ def _sort(self): item_order = np.argsort(item_starts) self._values = [self._values[idx] for idx in item_order] - def _entry_class_checker(self, value): + def _entry_class_checker(self, value) -> None: if self.entry_class is None: self.entry_class = value.entry_class @@ -102,7 +108,7 @@ def _entry_class_checker(self, value): or issubclass(value.entry_class, self.entry_class)): raise ValueError("All values must have the same class.") - def _shift(self, increment): + def _shift(self, increment)->None: for value in self: value._shift(increment) @@ -153,7 +159,7 @@ def labels(self) -> list[str]: return [] - def append(self, value, shift:bool = False, re_init = False): + def append(self:Sequence[SeqVar], value:SeqVar, shift:bool = False, re_init = False)->None: """Append a SequenceInterval to the list. After appending, the SequenceIntervals are re-sorted @@ -179,7 +185,7 @@ def append(self, value, shift:bool = False, re_init = False): self._values.append(value) self._sort() - def concat(self, intervals:list|Self): + def concat(self:Sequence[SeqVar], intervals:Sequence[SeqVar])->None: intervals = SequenceList(*intervals) increment = 0 @@ -192,7 +198,7 @@ def concat(self, intervals:list|Self): self._values = new_values - def remove(self, x): + def remove(self:Sequence[SeqVar], x:SeqVar)->None: """Remove a SequenceInterval from the list Args: @@ -203,7 +209,7 @@ def remove(self, x): if hasattr(x, "super_instance"): x.remove_superset() - def pop(self, x): + def pop(self:Sequence[SeqVar], x:SeqVar)->None: """Pop a SequenceInterval Args: From f99d575d1a8f3288c8b75f77de5ed3ffaf597362 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 11:33:58 -0400 Subject: [PATCH 076/104] warns for add and append on points --- src/aligned_textgrid/points/points.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/aligned_textgrid/points/points.py b/src/aligned_textgrid/points/points.py index f90e07a..d7bc4d8 100644 --- a/src/aligned_textgrid/points/points.py +++ b/src/aligned_textgrid/points/points.py @@ -108,7 +108,14 @@ def __contains__(self, item): def __getitem__(self, idex): warnings.warn("Indexing is not valid for a SequencePoint") - return None + return None + + def __add__(self, x): + warnings.warn("+ is not implemented for SequencePoint") + return self + + def append(self, x): + warnings.warn("append() is not implemented for SequencePoint") ## Properties @property From 672f508d116b837a7a18c44fc71ada930f68d008 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 11:54:21 -0400 Subject: [PATCH 077/104] more typing --- src/aligned_textgrid/sequences/sequences.py | 72 ++++++++++----------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index ec3f5fc..d38ebf5 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -35,8 +35,8 @@ class HierarchyMixins: @classmethod def set_superset_class( cls, - superset_class: type = None - ): + superset_class: type['SequenceInterval'] = None + ) -> None: """_Set the superset class_ Args: @@ -62,7 +62,7 @@ def set_superset_class( raise Exception(f"Unknown error setting {superset_class.__name__} as superset class of {cls.__name__}") @classmethod - def set_subset_class(cls, subset_class = None): + def set_subset_class(cls, subset_class:type['SequenceInterval'] = None)->None: """summary Args: @@ -90,9 +90,9 @@ def set_subset_class(cls, subset_class = None): class InstanceMixins(HierarchyMixins, WithinMixins): - def __add__(self, other): - - self_copy = self.entry_class(self) + def __add__(self, other:'SequenceInterval')->'SequenceInterval': + self_copy:SequenceInterval = self.entry_class(self) + self_copy.__dict__ = self.__dict__ if issubclass(self.entry_class, other.superset_class): self_copy.subset_list += [other] @@ -103,7 +103,7 @@ def __add__(self, other): raise ValueError("An added SequenceInterval must either be a subset or superset class of the original.") - def append(self, other): + def append(self, other:'SequenceInterval')->None: if issubclass(self.subset_class, other.entry_class): self.subset_list += [other] if self.intier and self.intier.within: @@ -112,7 +112,7 @@ def append(self, other): return raise ValueError("Appended SequenceInterval must be the subset class of the original.") - def set_super_instance(self, super_instance = None): + def set_super_instance(self, super_instance:'SequenceInterval' = None)->None: """Sets the specific superset relationship Args: @@ -132,7 +132,7 @@ def set_super_instance(self, super_instance = None): ## Subset Methods - def set_subset_list(self, subset_list = None): + def set_subset_list(self, subset_list:SequenceList['SequenceInterval'] = None)->None: """Appends all objects to the `subset_list` Args: @@ -155,7 +155,7 @@ def set_subset_list(self, subset_list = None): subset_class_set = set([type(x).__name__ for x in subset_list]) raise Exception(f"The subset_class was defined as {self.subset_class.__name__}, but provided subset_list contained {subset_class_set}") - def append_subset_list(self, subset_instance = None): + def append_subset_list(self, subset_instance:SequenceList['SequenceInterval'] = None)->None: """Append a single item to subset list Args: @@ -180,7 +180,7 @@ def append_subset_list(self, subset_instance = None): else: raise Exception(f"The subset_class was defined as {self.subset_class.__name__}, but provided subset_instance was {type(subset_instance).__name__}") - def remove_from_subset_list(self, subset_instance = None): + def remove_from_subset_list(self, subset_instance:'SequenceInterval' = None)->None: """Remove a sequence interval from the subset list Args: @@ -196,7 +196,7 @@ def remove_from_subset_list(self, subset_instance = None): self._set_subset_precedence() #self.validate() - def remove_superset(self): + def remove_superset(self)->None: """Remove the superset instance from the current subset class """ @@ -210,7 +210,7 @@ def remove_superset(self): - def _set_subset_precedence(self): + def _set_subset_precedence(self)->None: """summary Private method. Sorts subset list and re-sets precedence relationshops. @@ -225,7 +225,7 @@ def _set_subset_precedence(self): else: p.set_fol(self._subset_list[idx+1]) - def _set_within(self): + def _set_within(self)->None: """summary Private method. Sets within """ @@ -402,17 +402,17 @@ def __init__( self.intier = None - def __contains__(self, item): + def __contains__(self, item:'SequenceInterval')->bool: return item in self.subset_list - def __getitem__(self, idx): + def __getitem__(self, idx:int)->'SequenceInterval': return self.subset_list[idx] def __iter__(self): self.current = 0 return self - def __len__(self): + def __len__(self)->int: return len(self.subset_list) def __next__(self): @@ -425,7 +425,7 @@ def __next__(self): def index( self, - subset_instance + subset_instance:'SequenceInterval' ) -> int: """Returns subset instance index @@ -440,8 +440,8 @@ def index( def pop( self, - subset_instance - ): + subset_instance:'SequenceInterval' + ) -> bool: """Pop a sequence interval from the subset list Args: @@ -470,11 +470,11 @@ def __repr__(self) -> str: # properties @property - def subset_list(self): + def subset_list(self)->SequenceList['SequenceInterval']: return self._subset_list @subset_list.setter - def subset_list(self, intervals: list[Self]|SequenceList[Self]): + def subset_list(self, intervals: list[Self]|SequenceList[Self])->None: orig_values = self.subset_list intervals = SequenceList(*intervals) self.set_subset_list(intervals) @@ -484,34 +484,34 @@ def subset_list(self, intervals: list[Self]|SequenceList[Self]): orig.remove_superset() @property - def start(self): + def start(self)->float: return self._start @start.setter - def start(self, time): + def start(self, time:float): self._start = time @property - def end(self): + def end(self)->float: return self._end @end.setter - def end(self, time): + def end(self, time:float): self._end = time @property - def sub_starts(self): + def sub_starts(self)->np.array: return self.subset_list.starts @property - def sub_ends(self): + def sub_ends(self)->np.array: return self.subset_list.ends @property - def sub_labels(self): + def sub_labels(self) -> list[str]: return self.subset_list.labels - def _shift(self, increment): + def _shift(self, increment:float)->None: self.start += increment self.end += increment if len(self.subset_list) > 0: @@ -522,10 +522,10 @@ def duration(self) -> float: return self.end - self.start @property - def entry_class(self): + def entry_class(self)->type['SequenceInterval']: return self.__class__ - def cleanup(self): + def cleanup(self)->None: if isinstance(self.subset_class, Bottom): return if not len(self.subset_list) > 0: @@ -563,7 +563,7 @@ def cleanup(self): def fuse_rightwards( self, label_fun = lambda x, y: " ".join([x, y]) - ): + ) -> None: """Fuse the current segment with the following segment Args: @@ -594,7 +594,7 @@ def fuse_rightwards( def fuse_leftwards( self, label_fun = lambda x, y: " ".join([x, y]) - ): + )->None: """Fuse the current segment with the previous segment Args: @@ -622,10 +622,10 @@ def fuse_leftwards( else: raise Exception("Cannot fuse leftwards at right edge") - def fuse_rightward(self): + def fuse_rightward(self)->None: self.fuse_rightwards() - def fuse_leftward(self): + def fuse_leftward(self)->None: self.fuse_leftwards() ## Extensions and Saving From 0cb9cb47dc208c1fa4ce6976b0cf6c5973ad3fb2 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 12:01:03 -0400 Subject: [PATCH 078/104] better typing on custom_classes --- src/aligned_textgrid/custom_classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aligned_textgrid/custom_classes.py b/src/aligned_textgrid/custom_classes.py index c294f35..4c150bc 100644 --- a/src/aligned_textgrid/custom_classes.py +++ b/src/aligned_textgrid/custom_classes.py @@ -38,7 +38,7 @@ def custom_classes( class_list:list[str] = [], return_order: list[str] | list[int] | None = None, points:list[int] = [] -) -> list[Type[SequenceInterval]]: +) -> list[type[SequenceInterval]|type[SequencePoint]]: """Generate custom interval classes Passing `custom_classes()` a list of Sequence names wil return a list of From d15aecf42f48d4c67de7314680d400ea0d80a05d Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 12:37:15 -0400 Subject: [PATCH 079/104] more typing --- src/aligned_textgrid/mixins/mixins.py | 47 ++++++++++++++++++--------- src/aligned_textgrid/mixins/within.py | 27 ++++++++++----- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/aligned_textgrid/mixins/mixins.py b/src/aligned_textgrid/mixins/mixins.py index d42d710..e34c8f4 100644 --- a/src/aligned_textgrid/mixins/mixins.py +++ b/src/aligned_textgrid/mixins/mixins.py @@ -1,14 +1,24 @@ from praatio.utilities.constants import Interval, Point from praatio.data_classes.interval_tier import IntervalTier from praatio.data_classes.point_tier import PointTier -from typing import Type, Any -import numpy as np +from typing import Type, Any, TYPE_CHECKING, TypeVar import warnings +import sys +if sys.version_info >= (3,11): + from typing import Self +else: + from typing_extensions import Self + +if TYPE_CHECKING: + from aligned_textgrid import SequenceInterval, SequencePoint + +SeqType = TypeVar("SeqType", 'SequenceInterval', 'SequencePoint') +PraatioType = TypeVar("PraatioType", Interval, Point) class SequenceBaseClass: @classmethod - def _cast(cls, obj, re_init = False): + def _cast(cls, obj:SeqType, re_init = False)->SeqType: if not isinstance(obj, SequenceBaseClass): obj = cls(obj) return obj @@ -23,7 +33,7 @@ def _cast(cls, obj, re_init = False): return obj @classmethod - def _set_seq_type(cls, cls2): + def _set_seq_type(cls:SeqType, cls2:SeqType): cls._seq_type = cls2 class PrecedenceMixins: @@ -35,7 +45,7 @@ class PrecedenceMixins: """ @property - def first(self): + def first(self)->'SequenceInterval': if hasattr(self, "subset_list") and len(self.subset_list) > 0: return self.subset_list[0] if hasattr(self, "subset_list"): @@ -44,7 +54,7 @@ def first(self): raise AttributeError(f"{type(self).__name__} is not indexable.") @property - def last(self): + def last(self)->'SequenceInterval': if hasattr(self, "subset_list") and len(self.subset_list) > 0: return self.subset_list[-1] if hasattr(self, "subset_list"): @@ -53,7 +63,9 @@ def last(self): raise AttributeError(f"{type(self).__name__} is not indexable.") def set_fol( - self, next_int): + self:SeqType, + next_int:SeqType + )->None: """Sets the following instance Args: @@ -74,7 +86,10 @@ def set_fol( else: raise Exception(f"Following segment must be an instance of {type(self).__name__}") - def set_prev(self, prev_int): + def set_prev( + self:SeqType, + prev_int:SeqType + )->None: """Sets the previous intance Args: @@ -95,7 +110,7 @@ def set_prev(self, prev_int): else: raise Exception(f"Previous segment must be an instance of {type(self).__name__}") - def set_final(self): + def set_final(self)->None: """Sets the current object as having no `fol` entry While `self.fol` is defined for these entries, the actual @@ -107,7 +122,7 @@ def set_final(self): elif "Point" in self._seq_type.__name__: self.set_fol(type(self)(Point(None, "#"))) - def set_initial(self): + def set_initial(self)->None: """Sets the current object as having no `prev` entry While `self.prev` is defined for these entries, the actual @@ -128,16 +143,16 @@ class InTierMixins: ## Tier operations @property - def tier_index(self): + def tier_index(self:SeqType)->int: if not self.intier is None: return self.intier.index(self) else: return None def get_tierwise( - self, + self:SeqType, idx:int = 0 - ): + )->SeqType: """Get entry by relative tier index Returns a SequenceInterval or SequencePoint from an index position relative to @@ -167,14 +182,14 @@ def get_tierwise( else: return None - def return_praatio(self): + def return_praatio(self)->Interval|Point: if "Interval" in self._seq_type.__name__: return self.return_interval() if "Point" in self._seq_type.__name__: return self.return_point() - def return_interval(self) -> Interval: + def return_interval(self:'SequenceInterval') -> Interval: """Return current object as `Interval` Will be useful for saving back to textgrid @@ -184,7 +199,7 @@ def return_interval(self) -> Interval: """ return Interval(self.start, self.end, self.label) - def return_point(self) -> Point: + def return_point(self:'SequencePoint') -> Point: """Return current object as `Point` Returns: diff --git a/src/aligned_textgrid/mixins/within.py b/src/aligned_textgrid/mixins/within.py index bbf340b..ff36f6f 100644 --- a/src/aligned_textgrid/mixins/within.py +++ b/src/aligned_textgrid/mixins/within.py @@ -1,23 +1,34 @@ +from typing import TypeVar, Sequence, TYPE_CHECKING +if TYPE_CHECKING: + from aligned_textgrid import SequenceInterval, \ + SequenceTier,\ + SequencePointTier,\ + TierGroup,\ + AlignedTextGrid + +Within = TypeVar("Within", 'SequenceInterval', 'SequenceTier', 'SequencePointTier', 'TierGroup', 'AlignedTextGrid') +Contained = TypeVar("Contained",'SequenceInterval', 'SequenceTier', 'SequencePointTier', 'TierGroup') + class WithinMixins: @property - def within(self): + def within(self)->Within: if hasattr(self, "_within"): return self._within return None @within.setter - def within(self, obj): + def within(self, obj:Within)->None: self._within = obj @property - def contains(self): + def contains(self)->Sequence[Contained]: if hasattr(self, "_contains"): return self._contains return [] @contains.setter - def contains(self, new_contains: list): + def contains(self, new_contains: Sequence[Contained]): self._contains = new_contains for item in self._contains: if not hasattr(item, "_within"): @@ -27,12 +38,12 @@ def contains(self, new_contains: list): @property - def within_index(self): + def within_index(self)->int: if hasattr(self, "_within") and hasattr(self.within, "_contains"): return self.within.contains.index(self) return None - def _get_within_path(self, obj): + def _get_within_path(self, obj:Contained)->list[int]: path = [] if not obj.within_index is None: path += [obj.within_index] @@ -42,12 +53,12 @@ def _get_within_path(self, obj): return path @property - def within_path(self): + def within_path(self)->list[int]: path = self._get_within_path(obj = self) path.reverse() return path @property - def id(self): + def id(self)->str: path = self.within_path return "-".join([str(x) for x in path]) \ No newline at end of file From a67943e57dce11e82b70f148e18e284eea85d49a Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 12:37:38 -0400 Subject: [PATCH 080/104] typing --- src/aligned_textgrid/points/points.py | 11 +++++++---- src/aligned_textgrid/sequences/sequences.py | 5 ++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/aligned_textgrid/points/points.py b/src/aligned_textgrid/points/points.py index d7bc4d8..83e6f96 100644 --- a/src/aligned_textgrid/points/points.py +++ b/src/aligned_textgrid/points/points.py @@ -5,6 +5,7 @@ from aligned_textgrid.sequences.sequences import SequenceInterval import warnings import numpy as np +from typing import TYPE_CHECKING import sys if sys.version_info >= (3,11): @@ -12,6 +13,8 @@ else: from typing_extensions import Self +if TYPE_CHECKING: + from aligned_textgrid import SequencePointTier class SequencePoint(SequenceBaseClass, PrecedenceMixins, InTierMixins, WithinMixins): """Sequence Points @@ -83,12 +86,12 @@ def __init__( self.time = point.time self.label = point.label - self.intier = None - self.tiername = None + self.intier:'SequencePointTier|None' = None + self.tiername:str|None = None self.pointspool = None - self.fol = None - self.prev = None + self.fol:SequencePoint|None = None + self.prev:SequencePoint|None = None if self.label != "#": self.set_final() diff --git a/src/aligned_textgrid/sequences/sequences.py b/src/aligned_textgrid/sequences/sequences.py index d38ebf5..ff41bb9 100644 --- a/src/aligned_textgrid/sequences/sequences.py +++ b/src/aligned_textgrid/sequences/sequences.py @@ -15,12 +15,15 @@ import warnings import sys from collections.abc import Sequence +from typing import TYPE_CHECKING if sys.version_info >= (3,11): from typing import Self else: from typing_extensions import Self +if TYPE_CHECKING: + from aligned_textgrid import SequenceTier class HierarchyMixins: @@ -400,7 +403,7 @@ def __init__( self._subset_list = SequenceList() self.super_instance= None - self.intier = None + self.intier:'SequenceTier|None' = None def __contains__(self, item:'SequenceInterval')->bool: return item in self.subset_list From 615f6edebbf6efe337be8f675ca5edb5f3c1d03f Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 14:28:50 -0400 Subject: [PATCH 081/104] add seq_types to tiers and tier groups --- src/aligned_textgrid/mixins/tiermixins.py | 3 +++ src/aligned_textgrid/points/tiers.py | 2 ++ src/aligned_textgrid/sequences/tiers.py | 4 +++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index c27efc0..f930407 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -13,6 +13,9 @@ class TierMixins: first (SequenceInterval): The first entry in the tier. last (SequenceInterval): The last entry in the tier. """ + @classmethod + def _set_seq_type(cls, seq_type): + cls._seq_type = seq_type def __add__(self, new): if not isinstance(new, self.__class__): diff --git a/src/aligned_textgrid/points/tiers.py b/src/aligned_textgrid/points/tiers.py index b56902b..db6e15c 100644 --- a/src/aligned_textgrid/points/tiers.py +++ b/src/aligned_textgrid/points/tiers.py @@ -321,3 +321,5 @@ def __repr__(self): classes = [x.__name__ for x in self.entry_classes] return f"PointsGroup with {n_tiers} tiers. {repr(classes)}" +SequencePointTier._set_seq_type(SequencePoint) +PointsGroup._set_seq_type(SequencePoint) \ No newline at end of file diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 73a8a6f..1515447 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -524,4 +524,6 @@ def show_structure(self): print(f"{tab*(idx+1)}{tier.entry_class.__name__}(\n") if idx == len(self.tier_list)-1: print(f"{tab*(idx+2)}{tier.subset_class.__name__}(\n") - \ No newline at end of file + +SequenceTier._set_seq_type(SequenceInterval) +TierGroup._set_seq_type(SequenceInterval) \ No newline at end of file From 971dceea5f4371267905ba8a44f232b83ed8720c Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 14:29:30 -0400 Subject: [PATCH 082/104] seq_type on tier group --- src/aligned_textgrid/mixins/tiermixins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index f930407..be8c7ee 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -101,6 +101,9 @@ class TierGroupMixins: Attributes: []: Indexable and iterable """ + @classmethod + def _set_seq_type(cls, seq_type): + cls._seq_type = seq_type def _class_check(self, new): if len(self) != len(new): From 4183e52e07099c38d53c5ceccf7b7c634bf059b1 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 14:29:58 -0400 Subject: [PATCH 083/104] tier group appends --- src/aligned_textgrid/mixins/tiermixins.py | 48 +++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index be8c7ee..f97c95e 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -129,7 +129,55 @@ def __add__(self, new): return new_tg + def append(self:TierGroupType, new:TierType): + if not self._seq_type is new._seq_type: + raise ValueError(( + f"Only a tier of {self._seq_type.__name__} " + f"can be appended to a {self.__class__.__name__}" + )) + + possible_set = {SequenceBaseClass} + existing_set = set() + if len(self) > 0: + if hasattr(self[0], "subset_class"): + possible_classes = [ + [tier.subset_class, tier.superset_class] + for tier in self + ] + possible_flat = [cl for gr in possible_classes for cl in gr] + possible_set = set(possible_flat) + + existing_set = {tier.entry_class for tier in self} + + existing_matches = [ + issubclass(cl, new.entry_class) or issubclass(new.entry_class, cl) + for cl in existing_set + ] + + if any(existing_matches): + raise ValueError(( + f"This {self.__class__.__name__} already has a " + f"{new.entry_class.__name__} tier" + )) + + possible_matches = [ + issubclass(cl, new.entry_class) or issubclass(new.entry_class, cl) + for cl in possible_set + ] + + if not any(possible_matches): + raise ValueError(( + f"A tier with {new.entry_class.__name__} entry class " + "does not fit into the sequence hierarchy." + )) + + orig_tiers = self.tier_list + orig_tiers += [new] + + self.__init__(orig_tiers) + + pass def concat(self, new): self._class_check(new) From 56bf2c9b63ee94b6689249decd863bf59e60a328 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 14:30:09 -0400 Subject: [PATCH 084/104] more typing --- src/aligned_textgrid/mixins/tiermixins.py | 37 ++++++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index f97c95e..f9dd472 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -4,6 +4,27 @@ import re import warnings import numpy as np +import sys +from collections.abc import Sequence +if sys.version_info >= (3,11): + from typing import Self +else: + from typing_extensions import Self + +from typing import TypeVar, TYPE_CHECKING + +if TYPE_CHECKING: + from aligned_textgrid import SequenceInterval, \ + SequencePoint, \ + SequenceTier, \ + SequencePointTier,\ + TierGroup,\ + PointsGroup + +TierType = TypeVar("TierType", 'SequenceTier', 'SequencePointTier') +TierGroupType = TypeVar("TierGroupType", 'TierGroup', 'PointsGroup') + + class TierMixins: """Methods and attributes for Sequence Tiers @@ -17,13 +38,13 @@ class TierMixins: def _set_seq_type(cls, seq_type): cls._seq_type = seq_type - def __add__(self, new): + def __add__(self:TierType, new:TierType): if not isinstance(new, self.__class__): msg = f"A {type(self).__name__} can only be added to a {type(self).__name__}." if isinstance(new, SequenceBaseClass): msg += " Did you mean to use append()?" raise ValueError(msg) - if not issubclass(self.entry_class, new.entry_class): + if not (issubclass(self.entry_class, new.entry_class) or issubclass(new.entry_class, self.entry_class)): raise ValueError("Added tiers must have the same entry class") entries = self.sequence_list + new.sequence_list @@ -63,8 +84,8 @@ def append(self, new, re_relate = True): - def concat(self, new): - if not issubclass(self.entry_class, new.entry_class): + def concat(self:TierType, new:TierType): + if not (issubclass(self.entry_class, new.entry_class) or issubclass(new.entry_class, self.entry_class)): raise ValueError("Added tiers must have the same entry class") lhs = self.sequence_list @@ -75,7 +96,7 @@ def concat(self, new): self.sequence_list = lhs @property - def first(self): + def first(self) -> 'SequenceInterval|SequencePoint': if hasattr(self, "sequence_list") and len(self.sequence_list) > 0: return self.sequence_list[0] if hasattr(self, "sequence_list"): @@ -84,7 +105,7 @@ def first(self): raise AttributeError(f"{type(self).__name__} is not indexable.") @property - def last(self): + def last(self)->'SequenceInterval|SequencePoint': if hasattr(self, "sequence_list") and len(self.sequence_list) > 0: return self.sequence_list[-1] if hasattr(self, "sequence_list"): @@ -105,7 +126,7 @@ class TierGroupMixins: def _set_seq_type(cls, seq_type): cls._seq_type = seq_type - def _class_check(self, new): + def _class_check(self:TierGroupType, new:TierGroupType): if len(self) != len(new): raise ValueError("Original TierGroup and new TierGroup must have the same number of tiers.") @@ -116,7 +137,7 @@ def _class_check(self, new): if o != n: raise ValueError("Entry classes must be the same between added tier groups") - def __add__(self, new): + def __add__(self:TierGroupType, new:TierGroupType): self._class_check(new) From ddf0978c237a8a6aff0560e327e37ea17b820177 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 14:30:23 -0400 Subject: [PATCH 085/104] safer sequence list checks --- src/aligned_textgrid/sequence_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index 0f1d209..2eeb4d1 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -75,7 +75,7 @@ def __add__(self, other:Sequence[SeqVar]) -> Self: return self incoming_class = next(iter(unique_other_classes)) - if self.entry_class and not incoming_class.__name__ == self.entry_class.__name__: + if self.entry_class and not (issubclass(incoming_class, self.entry_class) or (issubclass(self.entry_class, incoming_class))): raise ValueError("All values in added list must have the same class as original list.") if not self.entry_class: From d5dc0e25e3b5eaafc1e6a82ae34c7f1d8ef4ff73 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 14:30:33 -0400 Subject: [PATCH 086/104] add append tests --- tests/test_add_append.py | 359 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 tests/test_add_append.py diff --git a/tests/test_add_append.py b/tests/test_add_append.py new file mode 100644 index 0000000..3bb3c5f --- /dev/null +++ b/tests/test_add_append.py @@ -0,0 +1,359 @@ +from aligned_textgrid import SequenceInterval, \ + SequencePoint, \ + SequenceTier, \ + SequencePointTier,\ + TierGroup,\ + PointsGroup,\ + custom_classes + +import pytest + +class TestSequenceInterval: + + def test_solo_add(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + + word = MyWord((0, 10, "a")) + phone1 = MyPhone((0, 5, "A")) + phone2 = MyPhone((5, 10, "AA")) + + # + breaks reference of the addee + word2 = word + + word += phone1 + phone2 += word + + assert phone1 in word + assert phone2 in word + assert not word2 is word + + assert phone1.fol is phone2 + + def test_point_add(self): + MyPoint, = custom_classes(["MyPoint"], points = [0]) + point1 = MyPoint((0,"a")) + point2 = MyPoint((1,"b")) + + with pytest.warns(): + point1 + point2 + + with pytest.warns(): + point1.append(point2) + + def test_solo_append(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word = MyWord((0, 10, "a")) + phone1 = MyPhone((0, 5, "A")) + phone2 = MyPhone((5, 10, "AA")) + + # append does not break reference + word2 = word + + word.append(phone1) + word.append(phone2) + + assert phone1 in word + assert phone2 in word + assert word2 is word + +class TestSequenceInTier: + + def test_no_tiergroup(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((10,20,"b")) + + phone1 = MyPhone((0,5,"A")) + phone2 = MyPhone((5,10,"AA")) + phone3 = MyPhone((10,15,"B")) + phone4 = MyPhone((15,20,"BB")) + + word_tier = SequenceTier([word1, word2]) + phone_tier = SequenceTier([phone2]) + + word1.append(phone2) + word1.append(phone1) + word2.append(phone3) + word2.append(phone4) + + assert phone1 in word1 + assert not phone1 in phone_tier + + assert phone1.within.fol.first is phone3 + + def test_tiergroup(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((10,20,"b")) + + phone1 = MyPhone((0,5,"A")) + phone2 = MyPhone((5,10,"AA")) + phone3 = MyPhone((10,15,"B")) + phone4 = MyPhone((15,20,"BB")) + + word_tier = SequenceTier([word1, word2]) + phone_tier = SequenceTier(entry_class=MyPhone) + + # relating tiers should percolate sequence appends + tier_group = TierGroup([word_tier, phone_tier]) + word1.append(phone1) + + assert phone1 in word1 + assert phone1 in phone_tier + + assert phone1.intier is phone_tier + + +class TestTiers: + + def test_sequence_tier_add(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((10,20,"b")) + + word_tier1 = SequenceTier([word1]) + word_tier2 = SequenceTier([word2]) + + word_tier_copy = word_tier1 + + # add breaks reference + word_tier1 += word_tier2 + + assert not word_tier_copy is word_tier1 + + for word in [word1, word2]: + assert word in word_tier1 + assert word.intier is word_tier1 + assert not word.intier is word_tier_copy + assert not word.intier is word_tier2 + + def test_sequence_tier_add_casting(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + class MyWordSub(MyWord): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + word1 = MyWord((0,10,"a")) + sub_word = MyWordSub((10,20,"b")) + seq_1 = SequenceInterval((20, 30, "c")) + + word_tier = SequenceTier([word1]) + + assert issubclass(MyWordSub, MyWord) + + word_tier += SequenceTier([sub_word]) + assert sub_word in word_tier + assert sub_word.entry_class is MyWord + + word_tier += SequenceTier([seq_1]) + assert seq_1 in word_tier + assert seq_1.entry_class is MyWord + + def test_sequence_tier_add_exception(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((10,20,"b")) + + phone1 = MyPhone((0,5, "AA")) + + word_tier1 = SequenceTier(entry_class=MyWord) + + with pytest.raises(ValueError): + word_tier1 += word1 + + with pytest.raises(ValueError): + phone_tier = SequenceTier([phone1]) + word_tier1 += phone_tier + + def test_sequence_tier_append(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((10,20,"b")) + + word_tier1 = SequenceTier(entry_class=MyWord) + + # append does not break reference + word_tier_copy = word_tier1 + + word_tier1.append(word1) + word_tier1.append(word2) + + assert word_tier_copy is word_tier1 + for word in [word1, word2]: + assert word in word_tier1 + assert word.intier is word_tier1 + + assert word1.fol is word2 + + def test_sequence_tier_append_casting(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + class MyWordSub(MyWord): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + word1 = MyWord((0,10,"a")) + sub_word = MyWordSub((10,20,"b")) + seq_1 = SequenceInterval((20, 30, "c")) + + word_tier = SequenceTier([word1]) + + word_tier.append(sub_word) + assert sub_word in word_tier + assert sub_word.entry_class is MyWord + + word_tier.append(seq_1) + assert seq_1 in word_tier + assert seq_1.entry_class is MyWord + + def test_sequence_tier_append_exception(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((10,20,"b")) + phone1 = MyPhone((0,5,"A")) + + seq_1 = SequenceInterval((0,5,"A")) + + word_tier1 = SequenceTier(entry_class=MyWord) + + with pytest.raises(ValueError): + word_tier1.append(phone1) + + def test_sequence_tier_concat(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((0,10,"b")) + + word_tier1 = SequenceTier([word1]) + word_tier2 = SequenceTier([word2]) + + word_tier1.concat(word_tier2) + + assert word1 in word_tier1 + assert word2 in word_tier2 + + assert word_tier1.ends.max() == 20 + + def test_point_tier_add(self): + MyPoint, = custom_classes(["MyPoint"], points=[0]) + point1 = MyPoint((0,"a")) + point2 = MyPoint((1, "b")) + point_tier1 = SequencePointTier([point1]) + point_tier2 = SequencePointTier([point2]) + + point_tier_copy = point_tier1 + + point_tier1 += point_tier2 + + assert not point_tier_copy is point_tier1 + + point1 in point_tier1 + point2 in point_tier2 + + def test_point_tier_append(self): + MyPoint, = custom_classes(["MyPoint"], points=[0]) + point1 = MyPoint((0,"a")) + point2 = MyPoint((1, "b")) + + point_tier = SequencePointTier(entry_class=MyPoint) + + point_tier_copy = point_tier + + point_tier.append(point1) + point_tier.append(point2) + + assert point_tier_copy is point_tier + assert point1 in point_tier + assert point2 in point_tier + + +class TestTierGroups: + def test_tier_group_append(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((10,20,"b")) + phone1 = MyPhone((0,5,"A")) + phone2 = MyPhone((5,10,"AA")) + phone3 = MyPhone((10, 15, "B")) + phone4 = MyPhone((15, 20, "BB")) + + word_tier1 = SequenceTier([word1]) + phone_tier1 = SequenceTier([phone1, phone2]) + + word_tier2 = SequenceTier([word2]) + phone_tier2 = SequenceTier([phone3, phone4]) + + + tier_group1 = TierGroup([word_tier1]) + tier_group1.append(phone_tier1) + + assert phone_tier1 in tier_group1 + assert phone_tier1.within_index == 1 + + assert phone1 in word1 + + tier_group2 = TierGroup([phone_tier2]) + tier_group2.append(word_tier2) + + assert word_tier2 in tier_group2 + assert word_tier2.within_index == 0 + assert phone3 in word2 + + def test_tier_group_add(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((10,20,"b")) + phone1 = MyPhone((0,5,"A")) + phone2 = MyPhone((5,10,"AA")) + phone3 = MyPhone((10, 15, "B")) + phone4 = MyPhone((15, 20, "BB")) + + word_tier1 = SequenceTier([word1]) + phone_tier1 = SequenceTier([phone1, phone2]) + + word_tier2 = SequenceTier([word2]) + phone_tier2 = SequenceTier([phone3, phone4]) + + + tier_group1 = TierGroup([word_tier1, phone_tier1]) + tier_group2 = TierGroup([word_tier2, phone_tier2]) + + tier_group1 += tier_group2 + + for word in [word1, word2]: + assert word in tier_group1.MyWord + for phone in [phone1, phone2, phone3, phone4]: + assert phone in tier_group1.MyPhone + + assert word1.fol is word2 + + def test_tier_group_concat(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((0,10,"b")) + phone1 = MyPhone((0,5,"A")) + phone2 = MyPhone((5,10,"AA")) + phone3 = MyPhone((0, 5, "B")) + phone4 = MyPhone((5, 10, "BB")) + + word_tier1 = SequenceTier([word1]) + phone_tier1 = SequenceTier([phone1, phone2]) + + word_tier2 = SequenceTier([word2]) + phone_tier2 = SequenceTier([phone3, phone4]) + + + tier_group1 = TierGroup([word_tier1, phone_tier1]) + tier_group2 = TierGroup([word_tier2, phone_tier2]) + + tier_group1.concat(tier_group2) + + assert tier_group1.xmax == 20 + for word in [word1, word2]: + assert word in tier_group1.MyWord + for phone in [phone1, phone2, phone3, phone4]: + assert phone in tier_group1.MyPhone + + assert word1.fol is word2 + pass + + pass \ No newline at end of file From 1084c21d11ec3f932253d4e90c2c711090c6108e Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 14:48:27 -0400 Subject: [PATCH 087/104] more tier append tests --- tests/test_add_append.py | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/test_add_append.py b/tests/test_add_append.py index 3bb3c5f..575b0b5 100644 --- a/tests/test_add_append.py +++ b/tests/test_add_append.py @@ -185,6 +185,32 @@ def test_sequence_tier_append(self): assert word1.fol is word2 + def test_sequence_tier_intg_append(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,10,"a")) + word2 = MyWord((10,20,"b")) + word3 = MyWord((20,30,"c")) + phone1 = MyPhone((0,10,"A")) + phone2 = MyPhone((10,15,"B")) + phone3 = MyPhone((20, 30, "C")) + + word2.append(phone2) + word3.append(phone3) + + word_tier = SequenceTier([word1]) + phone_tier = SequenceTier([phone1]) + + tier_group = TierGroup([word_tier, phone_tier]) + + assert not phone2 in phone_tier + word_tier.append(word2) + assert phone2 in phone_tier + + assert not word3 in word_tier + phone_tier.append(phone3) + assert word3 in word_tier + + def test_sequence_tier_append_casting(self): MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) class MyWordSub(MyWord): @@ -214,10 +240,14 @@ def test_sequence_tier_append_exception(self): seq_1 = SequenceInterval((0,5,"A")) word_tier1 = SequenceTier(entry_class=MyWord) + word_tier2 = SequenceTier([word1]) with pytest.raises(ValueError): word_tier1.append(phone1) + with pytest.raises(ValueError): + word_tier1.append(word_tier2) + def test_sequence_tier_concat(self): MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) word1 = MyWord((0,10,"a")) @@ -354,6 +384,39 @@ def test_tier_group_concat(self): assert phone in tier_group1.MyPhone assert word1.fol is word2 + + +class TestCleanups: + def test_sequence_cleanup(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,20,"a")) + phone1 = MyPhone((2,5,"A")) + phone2 = MyPhone((10,15,"AA")) + + word1.append(phone1) + word1.append(phone2) + + assert len(word1)==2 + + word1.cleanup() + assert len(word1)==5 + assert word1.first.label == "" + assert word1.last.label == "" + assert phone1.within_index == 1 + + pass + def test_tier_cleanups(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((0,15,"a")) + word2 = MyWord((20,25,"b")) + + word_tier = SequenceTier([word1, word2]) + assert len(word_tier) == 2 + + word_tier.cleanup() + assert len(word_tier) == 3 + + pass \ No newline at end of file From 38573e815bfeba800d83233cb7d845ce9decf40d Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 15:10:16 -0400 Subject: [PATCH 088/104] maintain reference when appending to atg --- src/aligned_textgrid/aligned_textgrid.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/aligned_textgrid/aligned_textgrid.py b/src/aligned_textgrid/aligned_textgrid.py index bb5732b..a7e4241 100644 --- a/src/aligned_textgrid/aligned_textgrid.py +++ b/src/aligned_textgrid/aligned_textgrid.py @@ -375,14 +375,13 @@ def xmax(self): def append(self, tier_group:TierGroup): new_classes = self._reclone_classes(tier_group.entry_classes) - new_tiers = TierGroup( - [ - SequenceTier(t, e) - for t,e in zip(tier_group, new_classes) - ] - ) + for cl, tier in zip(new_classes, tier_group): + entries = [cl._cast(i) for i in tier] + tier.__init__(entries) + + tier_group.__init__(tier_group) - self.tier_groups.append(new_tiers) + self.tier_groups.append(tier_group) self.entry_classes = [[tier.entry_class for tier in tg] for tg in self.tier_groups] def cleanup(self): From 012ddb040d65cc39110ce90936c7e482ca09f964 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 15:19:32 -0400 Subject: [PATCH 089/104] cleanup tests --- tests/test_add_append.py | 73 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/tests/test_add_append.py b/tests/test_add_append.py index 575b0b5..adda6fc 100644 --- a/tests/test_add_append.py +++ b/tests/test_add_append.py @@ -4,7 +4,9 @@ SequencePointTier,\ TierGroup,\ PointsGroup,\ + AlignedTextGrid,\ custom_classes +import numpy as np import pytest @@ -404,9 +406,6 @@ def test_sequence_cleanup(self): assert word1.last.label == "" assert phone1.within_index == 1 - - pass - def test_tier_cleanups(self): MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) word1 = MyWord((0,15,"a")) @@ -416,7 +415,73 @@ def test_tier_cleanups(self): assert len(word_tier) == 2 word_tier.cleanup() - assert len(word_tier) == 3 + assert len(word_tier) == 3 + + def test_tier_group_cleanups(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((2,15,"a")) + word2 = MyWord((20,25,"b")) + phone1 = MyPhone((2,5,"A")) + phone2 = MyPhone((20,22,"BB")) + + tier_group = TierGroup( + [ + SequenceTier([word1, word2]), + SequenceTier([phone1, phone2]) + ] + ) + + for tier in tier_group: + assert len(tier) == 2 + tier_group.cleanup() + for tier in tier_group: + assert len(tier) > 2 + + assert len(word1) == 2 + assert len(word2) == 2 + + starts = np.array([tier.xmin for tier in tier_group]) + ends = np.array([tier.xmax for tier in tier_group]) + assert np.allclose(*starts) + assert np.allclose(*ends) + + + def test_atg_cleanup(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + word1 = MyWord((2,15,"a")) + word2 = MyWord((20,25,"b")) + word3 = MyWord((0,15,"a")) + word4 = MyWord((20,22,"b")) + phone1 = MyPhone((2,5,"A")) + phone2 = MyPhone((20,25,"BB")) + phone3 = MyPhone((2,5,"A")) + phone4 = MyPhone((20,22,"BB")) + + tg1 = TierGroup([ + SequenceTier([word1, word2]), + SequenceTier([phone1, phone2]) + ]) + + tg2 = TierGroup([ + SequenceTier([word3, word4]), + SequenceTier([phone3, phone4]) + ]) + + atg = AlignedTextGrid() + atg.append(tg1) + atg.append(tg2) + + assert tg1.xmin != tg2.xmin + assert tg1.xmax != tg2.xmax + + atg.cleanup() + + assert tg1 in atg + assert tg2 in atg + + assert tg1.xmin == tg2.xmin + assert tg1.xmax == tg2.xmax + pass pass \ No newline at end of file From 82f130d9cb4618fdc925f73bc2c060676d141a29 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 15:19:40 -0400 Subject: [PATCH 090/104] typo catch --- src/aligned_textgrid/aligned_textgrid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aligned_textgrid/aligned_textgrid.py b/src/aligned_textgrid/aligned_textgrid.py index a7e4241..8e71063 100644 --- a/src/aligned_textgrid/aligned_textgrid.py +++ b/src/aligned_textgrid/aligned_textgrid.py @@ -415,7 +415,7 @@ def cleanup(self): continue start = tg.xmax - end = tg_starts.max() + end = tg_ends.max() tg_classes = tg.entry_classes From b8f79ad74542970c73a8bf2485b2187960089372 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 15:20:04 -0400 Subject: [PATCH 091/104] move ends --- src/aligned_textgrid/sequence_list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index 2eeb4d1..96f8ac5 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -146,11 +146,11 @@ def ends(self) -> np.array: if len(self) < 1: return np.array([]) - if hasattr(self[0], "end"): - return np.array([x.end for x in self]) - if hasattr(self[0], "time"): return np.array([x.time for x in self]) + + if hasattr(self[0], "end"): + return np.array([x.end for x in self]) @property def labels(self) -> list[str]: From 5955e1f857e491bd7513b1059f23a42f7da3218a Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 15:20:21 -0400 Subject: [PATCH 092/104] xmax fix --- src/aligned_textgrid/sequences/tiers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 1515447..1de3024 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -446,7 +446,7 @@ def xmin(self): @property def xmax(self): - return np.array([tier.xmax for tier in self.tier_list]).min() + return np.array([tier.xmax for tier in self.tier_list]).max() def project_up(self, interval:SequenceInterval): if issubclass(interval.superset_class, Top): From 3d196c66f478f96dd97a27f6a43d74f8d1db4d8c Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 16:09:49 -0400 Subject: [PATCH 093/104] safer max --- src/aligned_textgrid/sequence_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index 96f8ac5..1ce3428 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -190,7 +190,7 @@ def concat(self:Sequence[SeqVar], intervals:Sequence[SeqVar])->None: increment = 0 if len(self.ends) > 0: - increment = self.ends[-1] + increment = self.ends.max() intervals._shift(increment) From 53051460893bf087228982618f69e3e42c2e8fcb Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 16:10:31 -0400 Subject: [PATCH 094/104] rewrite tier group concat --- src/aligned_textgrid/mixins/tiermixins.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index f9dd472..91e25e3 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -200,12 +200,17 @@ def append(self:TierGroupType, new:TierType): pass - def concat(self, new): + def concat(self, new:'TierGroup'): self._class_check(new) - - _ = [ - t1.concat(t2) for t1, t2 in zip(self, new) - ] + increment = self.xmax + for t1, t2 in zip(self, new): + for interval in t2: + if interval.super_instance is not None: + continue + if interval in t1: + continue + interval._shift(increment) + t1.append(interval) From e68622a64788fca659b5f48009cc2e266a59d494 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 16:17:14 -0400 Subject: [PATCH 095/104] double run atg cleanup --- tests/test_add_append.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_add_append.py b/tests/test_add_append.py index adda6fc..67ed0c1 100644 --- a/tests/test_add_append.py +++ b/tests/test_add_append.py @@ -481,6 +481,16 @@ def test_atg_cleanup(self): assert tg1.xmin == tg2.xmin assert tg1.xmax == tg2.xmax + + atg.cleanup() + + assert tg1 in atg + assert tg2 in atg + + assert tg1.xmin == tg2.xmin + assert tg1.xmax == tg2.xmax + + pass From 92bf6e7a3f9ec0d75be938718925db5c360565f4 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 16:19:38 -0400 Subject: [PATCH 096/104] double run atg cleanup --- tests/test_add_append.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/test_add_append.py b/tests/test_add_append.py index 67ed0c1..943cda1 100644 --- a/tests/test_add_append.py +++ b/tests/test_add_append.py @@ -482,13 +482,18 @@ def test_atg_cleanup(self): assert tg1.xmin == tg2.xmin assert tg1.xmax == tg2.xmax + xmins = [tg.xmin for tg in atg] + xmaxes = [tg.xmax for tg in atg] + atg.cleanup() + xmins2 = [tg.xmin for tg in atg] + xmaxes2 = [tg.xmax for tg in atg] - assert tg1 in atg - assert tg2 in atg + for orig, new in zip(xmins, xmins2): + orig == new - assert tg1.xmin == tg2.xmin - assert tg1.xmax == tg2.xmax + for orig, new in zip(xmaxes, xmaxes2): + orig == new pass From cff5a783e2de74dcfce6692cca454f97c267adc3 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 16:30:09 -0400 Subject: [PATCH 097/104] test project up --- tests/test_add_append.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_add_append.py b/tests/test_add_append.py index 943cda1..ac6ac53 100644 --- a/tests/test_add_append.py +++ b/tests/test_add_append.py @@ -58,6 +58,19 @@ def test_solo_append(self): assert phone2 in word assert word2 is word + def test_solo_exceptions(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + YourWord, YourPhone = custom_classes(["YourWord", "YourPhone"]) + word = MyWord((0, 10, "a")) + phone = YourPhone((0,5,"A")) + + with pytest.raises(ValueError): + word += [phone] + + with pytest.raises(ValueError): + word.append(phone) + + class TestSequenceInTier: def test_no_tiergroup(self): @@ -445,6 +458,23 @@ def test_tier_group_cleanups(self): assert np.allclose(*starts) assert np.allclose(*ends) + def test_up_copy(self): + MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) + phone1 = MyPhone((0,5,"A")) + + tier_group = TierGroup([ + SequenceTier(entry_class=MyWord), + SequenceTier([phone1]) + ]) + + assert len(tier_group.MyWord) == 0 + + tier_group.cleanup() + + assert len(tier_group.MyWord) == 1 + + assert phone1 in tier_group.MyWord.first + def test_atg_cleanup(self): MyWord, MyPhone = custom_classes(["MyWord", "MyPhone"]) From bc2cc180d9402e5e6bbcc6d9419983910c946244 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 16:34:09 -0400 Subject: [PATCH 098/104] add for sequence interval doesn;t need to be listed --- tests/test_add_append.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_add_append.py b/tests/test_add_append.py index ac6ac53..2d84720 100644 --- a/tests/test_add_append.py +++ b/tests/test_add_append.py @@ -65,7 +65,7 @@ def test_solo_exceptions(self): phone = YourPhone((0,5,"A")) with pytest.raises(ValueError): - word += [phone] + word += phone with pytest.raises(ValueError): word.append(phone) From 3e5738f1063f62dc11892324b3b86947d4842df6 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 17:00:38 -0400 Subject: [PATCH 099/104] sequence list doc strings --- src/aligned_textgrid/sequence_list.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/aligned_textgrid/sequence_list.py b/src/aligned_textgrid/sequence_list.py index 1ce3428..de966ed 100644 --- a/src/aligned_textgrid/sequence_list.py +++ b/src/aligned_textgrid/sequence_list.py @@ -18,7 +18,7 @@ class SequenceList(Sequence): remains sorted Args: - *args (SequenceInterval, SequencePoint): + *args ('SequenceInterval|SequencePoint'): SequenceIntervals or SequencePoints Attributes: @@ -186,6 +186,17 @@ def append(self:Sequence[SeqVar], value:SeqVar, shift:bool = False, re_init = Fa self._sort() def concat(self:Sequence[SeqVar], intervals:Sequence[SeqVar])->None: + """Concatenate two sequence lists + + In concatenation, the time values of `intervals` are + time shfted by the end time of the original + SequenceList. + + Args: + intervals (Sequence[SeqVar]): + A list or SequenceList of + SequenceIntervals or SequencePoints + """ intervals = SequenceList(*intervals) increment = 0 From 2fd7b3cfcac27ccd5723a98a832d8025e2a74101 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 17:00:54 -0400 Subject: [PATCH 100/104] add sequence list to reference --- docs/_quarto.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/_quarto.yml b/docs/_quarto.yml index b6b5807..e6cb99e 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -121,7 +121,10 @@ quartodoc: don't have hierarchical relationships defined. contents: - package: aligned_textgrid.points.points - name: SequencePoint + name: SequencePoint + - title: SequenceList + contents: + - SequenceList - title: Tiers desc: Tiers Classes - subtitle: Common Features From 16aeca41a089deaa9121610e6c614ce3ad6223a4 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 17:01:01 -0400 Subject: [PATCH 101/104] types and docstrings --- src/aligned_textgrid/aligned_textgrid.py | 118 ++++++++++++++++------- 1 file changed, 84 insertions(+), 34 deletions(-) diff --git a/src/aligned_textgrid/aligned_textgrid.py b/src/aligned_textgrid/aligned_textgrid.py index 8e71063..3ac728b 100644 --- a/src/aligned_textgrid/aligned_textgrid.py +++ b/src/aligned_textgrid/aligned_textgrid.py @@ -356,24 +356,64 @@ def _set_group_names(self): setattr(self, name, self.tier_groups[idx]) @property - def tier_names(self): + def tier_names(self) -> list[str]: if len(self) == 0: raise ValueError('No tier names in an empty TextGrid.') return [x.tier_names for x in self.tier_groups] @property - def xmin(self): + def xmin(self)->np.array: if len(self) == 0: raise ValueError('No minimum time for empty TextGrid.') return np.array([tgroup.xmin for tgroup in self.tier_groups]).min() @property - def xmax(self): + def xmax(self)->np.array: if len(self) == 0: raise ValueError('No maximum time for empty TextGrid.') return np.array([tgroup.xmax for tgroup in self.tier_groups]).max() def append(self, tier_group:TierGroup): + """Append a new TierGroup to an existing + AlignedTextGrid. + + Examples: + ```{python} + #| warning: false + from aligned_textgrid import Word, Phone, SequenceTier, TierGroup, AlignedTextGrid + + speaker1 = TierGroup([ + SequenceTier([ + Word((0,10, "Hi")) + ]), + SequenceTier([ + Phone((0,5, "HH")), + Phone((5,10, "AY")) + ]) + ]) + + speaker2 = TierGroup([ + SequenceTier([ + Word((10,20, "Hi")) + ]), + SequenceTier([ + Phone((10,15, "HH")), + Phone((15,20, "AY")) + ]) + ]) + + atg = AlignedTextGrid() + + atg.append(speaker1) + atg.append(speaker2) + + print(atg) + ``` + + Args: + tier_group (TierGroup): + The TierGroup to append to the AlignedTextGrid + """ new_classes = self._reclone_classes(tier_group.entry_classes) for cl, tier in zip(new_classes, tier_group): entries = [cl._cast(i) for i in tier] @@ -384,45 +424,55 @@ def append(self, tier_group:TierGroup): self.tier_groups.append(tier_group) self.entry_classes = [[tier.entry_class for tier in tg] for tg in self.tier_groups] - def cleanup(self): - for tg in self.tier_groups: - tg.cleanup() + def cleanup(self)->None: + """Cleanup gaps in AlignedTextGrid - interval_tgs = [tg for tg in self if isinstance(tg, TierGroup)] - tg_starts = np.array([tg.xmin for tg in interval_tgs]) - tg_ends = np.array([tg.xmax for tg in interval_tgs]) + If any tiers have time gaps between + intervals, missing subset or superset intervals + or TierGroups with different start and ent times, + this will clean them up by adding intervals with + a blank label. - if np.allclose(tg_starts.min(), tg_starts.max()) and \ - np.allclose(tg_ends.min(), tg_ends.max()): - return - - for tg in self.tier_groups: - if np.allclose(tg.xmin, tg_starts.min()): - continue + """ + with warnings.simplefilter("ignore"): + for tg in self.tier_groups: + tg.cleanup() + + interval_tgs = [tg for tg in self if isinstance(tg, TierGroup)] + tg_starts = np.array([tg.xmin for tg in interval_tgs]) + tg_ends = np.array([tg.xmax for tg in interval_tgs]) - start = tg_starts.min() - end = tg.xmin + if np.allclose(tg_starts.min(), tg_starts.max()) and \ + np.allclose(tg_ends.min(), tg_ends.max()): + return + + for tg in self.tier_groups: + if np.allclose(tg.xmin, tg_starts.min()): + continue - tg_classes = tg.entry_classes + start = tg_starts.min() + end = tg.xmin - empty_intervals = [c((start, end, "")) for c in tg_classes] - for tier, interval in zip(tg, empty_intervals): - tier.append(interval) + tg_classes = tg.entry_classes - - for tg in self.tier_groups: - if np.allclose(tg.xmax, tg_starts.max()): - continue + empty_intervals = [c((start, end, "")) for c in tg_classes] + for tier, interval in zip(tg, empty_intervals): + tier.append(interval) - start = tg.xmax - end = tg_ends.max() + + for tg in self.tier_groups: + if np.allclose(tg.xmax, tg_starts.max()): + continue - tg_classes = tg.entry_classes - - empty_intervals = [c((start, end, "")) for c in tg_classes] - for tier, interval in zip(tg, empty_intervals): - tier.append(interval) - + start = tg.xmax + end = tg_ends.max() + + tg_classes = tg.entry_classes + + empty_intervals = [c((start, end, "")) for c in tg_classes] + for tier, interval in zip(tg, empty_intervals): + tier.append(interval) + def shift( self, increment: float From 5739f9fa975d28dd3f52647842ec14e20b699cb7 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Fri, 21 Jun 2024 17:13:26 -0400 Subject: [PATCH 102/104] tiers docstrings --- src/aligned_textgrid/sequences/tiers.py | 82 +++++++++++++++---------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index 1de3024..d9278ef 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -9,6 +9,7 @@ from aligned_textgrid.sequence_list import SequenceList from aligned_textgrid.mixins.tiermixins import TierMixins, TierGroupMixins from aligned_textgrid.mixins.within import WithinMixins +from aligned_textgrid.sequence_list import SequenceList import numpy as np from typing import Type from collections.abc import Sequence @@ -29,7 +30,7 @@ class SequenceTier(Sequence, TierMixins, WithinMixins): Args: tier (list[Interval] | list[SequenceInterval] | IntervalTier | Self, optional): - A list of interval entries. Defaults to `[]`. + A list of interval entries. Defaults to `SequenceList()`. entry_class (Type[SequenceInterval], optional): The sequence class for this tier. Defaults to SequenceInterval. @@ -50,16 +51,26 @@ class SequenceTier(Sequence, TierMixins, WithinMixins): ``` Attributes: - sequence_list (list[SequenceInterval]): + sequence_list (SequenceList[SequenceInterval]): + A `SequenceList` of intervals in the tier. entry_class (Type[SequenceInterval]): + The entry class of the tier superset_class (Type[SequenceInterval]): + The superset class of the tier subset_class (Type[SequenceInterval]): + The subset class of the tier starts (np.ndarray[np.float64]): + An array of start times for all intervals ends (np.ndarray[np.float64]): + An array of end times for all intervals labels (list[str]): + A list of the labels of all intervals xmin (float): + The minimum start time of the tier xmax (float): + The minumum end time of the tier name (str): + The name of the tier [] : Indexable. Returns a SequenceInterval : Iterable """ @@ -101,25 +112,25 @@ def __init__( def __set_classes( self, - entry_class - ): + entry_class:type[SequenceInterval] + )->None: self.entry_class = entry_class self.superset_class = self.entry_class.superset_class self.subset_class = self.entry_class.subset_class - def __build_sequence_list(self): + def __build_sequence_list(self)->None: intervals = [self.entry_class._cast(entry) for entry in self.entry_list] self.sequence_list = SequenceList(*intervals) - def __getitem__(self, idx): + def __getitem__(self, idx:int)->SequenceInterval: return self.sequence_list[idx] - def __len__(self): + def __len__(self)->int: return len(self.sequence_list) - def __set_precedence(self): + def __set_precedence(self)->None: for idx,seq in enumerate(self._sequence_list): self.__set_intier(seq) if idx == 0: @@ -135,8 +146,8 @@ def __set_precedence(self): def __set_intier( self, - entry - ): + entry:SequenceInterval + )->None: """ Sets the intier attribute of the entry """ @@ -145,8 +156,8 @@ def __set_intier( def pop( self, - entry - ): + entry:SequenceInterval + )->None: """Pop an interval Args: @@ -164,16 +175,16 @@ def __repr__(self): return f"Sequence tier of {self.entry_class.__name__}; .superset_class: {self.superset_class.__name__}; .subset_class: {self.subset_class.__name__}" @property - def sequence_list(self): + def sequence_list(self)->SequenceList: return self._sequence_list @sequence_list.setter - def sequence_list(self, new): + def sequence_list(self, new:Sequence): self._sequence_list = SequenceList(*new) self.__set_precedence() @property - def starts(self): + def starts(self)->np.array: return np.array([x.start for x in self.sequence_list]) @starts.setter @@ -185,7 +196,7 @@ def starts(self, times): i.start = t @property - def ends(self): + def ends(self)->np.array: return np.array([x.end for x in self.sequence_list]) @ends.setter @@ -196,29 +207,29 @@ def ends(self, times): for t, i in zip(times, self.sequence_list): i.end = t - def _shift(self, increment): + def _shift(self, increment:float) -> None: self.starts += increment self.ends += increment @property - def labels(self): + def labels(self)->list[str]: return [x.label for x in self.sequence_list] @property - def xmin(self): + def xmin(self)->float: if len(self.sequence_list) > 0: return self.sequence_list.starts.min() else: return None @property - def xmax(self): + def xmax(self)->float: if len(self.sequence_list) > 0: return self.sequence_list.ends.max() else: return None - def cleanup(self): + def cleanup(self)->None: """ Insert empty intervals where there are gaps in the existing tier. """ @@ -365,7 +376,7 @@ def __init__( def __getitem__( self, idx: int|list - ): + )->SequenceTier: if type(idx) is int: return self.tier_list[idx] if len(idx) != len(self): @@ -376,10 +387,10 @@ def __getitem__( out_list.append(tier[x]) return(out_list) - def __len__(self): + def __len__(self)->int: return len(self.tier_list) - def __repr__(self): + def __repr__(self)->str: n_tiers = len(self.tier_list) classes = [x.__name__ for x in self.entry_classes] return f"TierGroup with {n_tiers} tiers. {repr(classes)}" @@ -433,22 +444,25 @@ def _arrange_tiers( return(top_to_bottom) @property - def entry_classes(self): + def entry_classes(self)->list[type[SequenceInterval]]: return [x.entry_class for x in self.tier_list] @property - def tier_names(self): + def tier_names(self)->list[str]: return [x.name for x in self.tier_list] @property - def xmin(self): + def xmin(self)->float: return np.array([tier.xmin for tier in self.tier_list]).min() @property - def xmax(self): + def xmax(self)->float: return np.array([tier.xmax for tier in self.tier_list]).max() - def project_up(self, interval:SequenceInterval): + def _project_up(self, interval:SequenceInterval)->None: + """ + Copy super instance up + """ if issubclass(interval.superset_class, Top): return up_index = interval.intier.within_index - 1 @@ -464,7 +478,11 @@ def project_up(self, interval:SequenceInterval): up_tier.append(new_interval) - def cleanup(self): + def cleanup(self) -> None: + """ + This will fill any gaps between intervals with intervals + with an empty label. + """ for idx, tier in enumerate(self): if issubclass(tier.subset_class, Bottom): break @@ -474,7 +492,7 @@ def cleanup(self): for idx, tier in enumerate(reversed(self)): for interval in tier: - self.project_up(interval) + self._project_up(interval) for tier in self: tier.cleanup() @@ -485,7 +503,7 @@ def cleanup(self): def shift( self, increment: float - ): + )->None: """Shift the start and end times of all intervals within the TierGroup by the increment size From 3c5ca4c18a36adf0806a10e1884b16a4ad7bb5d6 Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 24 Jun 2024 15:52:47 -0400 Subject: [PATCH 103/104] warnings filter error --- src/aligned_textgrid/aligned_textgrid.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aligned_textgrid/aligned_textgrid.py b/src/aligned_textgrid/aligned_textgrid.py index 3ac728b..054cab8 100644 --- a/src/aligned_textgrid/aligned_textgrid.py +++ b/src/aligned_textgrid/aligned_textgrid.py @@ -434,7 +434,8 @@ def cleanup(self)->None: a blank label. """ - with warnings.simplefilter("ignore"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") for tg in self.tier_groups: tg.cleanup() From 079cef217adde847fb0da1665a22a3bff0d68efd Mon Sep 17 00:00:00 2001 From: JoFrhwld Date: Mon, 24 Jun 2024 16:07:28 -0400 Subject: [PATCH 104/104] docs --- docs/_quarto.yml | 5 ++ src/aligned_textgrid/custom_classes.py | 63 ++++++++-------- src/aligned_textgrid/mixins/mixins.py | 6 ++ src/aligned_textgrid/mixins/tiermixins.py | 91 ++++++++++++++++++++++- src/aligned_textgrid/mixins/within.py | 15 ++++ src/aligned_textgrid/points/tiers.py | 14 ++-- src/aligned_textgrid/sequences/tiers.py | 8 ++ 7 files changed, 162 insertions(+), 40 deletions(-) diff --git a/docs/_quarto.yml b/docs/_quarto.yml index e6cb99e..ad2745a 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -60,6 +60,7 @@ format: light: flatly dark: darkly toc: true + code-annotations: hover # tell quarto to read the generated sidebar metadata-files: @@ -99,6 +100,8 @@ quartodoc: These classes define common attributes and methods available to intervals and points. contents: + - package: aligned_textgrid.mixins.within + name: WithinMixins - package: aligned_textgrid.mixins.mixins name: PrecedenceMixins - package: aligned_textgrid.mixins.mixins @@ -146,6 +149,8 @@ quartodoc: name: SequencePointTier - package: aligned_textgrid.sequences.tiers name: TierGroup + - package: aligned_textgrid.points.tiers + name: PointsGroup - title: TextGrids desc: TextGrid Classes contents: diff --git a/src/aligned_textgrid/custom_classes.py b/src/aligned_textgrid/custom_classes.py index 4c150bc..1669264 100644 --- a/src/aligned_textgrid/custom_classes.py +++ b/src/aligned_textgrid/custom_classes.py @@ -45,44 +45,43 @@ def custom_classes( `SequenceInterval` subclasses with those names. The first name passed to `class_list` will be at the top of the hierarchy, the second name will be the subset class of the first, and so on. + + Examples: - To change the order in which the custom classes are *returned*, specify `return_order` - with either indices or class names. For example, if you have Words, Syllables, and Phones in - a hierarchical relationship in a textgrid, you can run the following: - - ```python - custom_classes(["Word", "Syllable", "Phone"]) - # [alignedTextGrid.custom_classes.Word, - # alignedTextGrid.custom_classes.Syllable, - # alignedTextGrid.custom_classes.Phone] - ``` - - But if the order of the textgrid tiers has Word as the bottom tier and Phone as the top, you can specify `return_order` - like so: - - ```python - custom_classes( - class_list = ["Word", "Syllable", "Phone"], - return_order = [2, 1, 0] - # or - # return_order = ["Phone", "Syllable", "Word] - ) - # [alignedTextGrid.custom_classes.Phone, - # alignedTextGrid.custom_classes.Syllable, - # alignedTextGrid.custom_classes.Word] - ``` + To change the order in which the custom classes are *returned*, specify `return_order` + with either indices or class names. For example, if you have Words, Syllables, and Phones in + a hierarchical relationship in a textgrid, you can run the following: + + + ```{python} + from aligned_textgrid import custom_classes + + custom_classes(["Word", "Syllable", "Phone"]) + ``` - This way, you can use `custom_classes()` directly as the `entry_classes` argument in `AlignedTextGrid` + But if the order of the textgrid tiers has Word as the bottom tier and Phone as the top, you can specify `return_order` + like so: - ```python - AlignedTextGrid( - textgrid_path = "syllables.TextGrid", - entry_classes = custom_classes( + ```{python} + custom_classes( class_list = ["Word", "Syllable", "Phone"], return_order = [2, 1, 0] + # or + # return_order = ["Phone", "Syllable", "Word] ) - ) - ``` + ``` + + This way, you can use `custom_classes()` directly as the `entry_classes` argument in `AlignedTextGrid` + + ```python + AlignedTextGrid( + textgrid_path = "syllables.TextGrid", + entry_classes = custom_classes( + class_list = ["Word", "Syllable", "Phone"], + return_order = [2, 1, 0] + ) + ) + ``` Args: class_list (list[str], optional): diff --git a/src/aligned_textgrid/mixins/mixins.py b/src/aligned_textgrid/mixins/mixins.py index e34c8f4..8e03039 100644 --- a/src/aligned_textgrid/mixins/mixins.py +++ b/src/aligned_textgrid/mixins/mixins.py @@ -183,6 +183,12 @@ def get_tierwise( return None def return_praatio(self)->Interval|Point: + """Return the correct `praatio` class. + + Returns: + (Interval|Point): + A `praatio` Interval or Point. + """ if "Interval" in self._seq_type.__name__: return self.return_interval() diff --git a/src/aligned_textgrid/mixins/tiermixins.py b/src/aligned_textgrid/mixins/tiermixins.py index 91e25e3..4243127 100644 --- a/src/aligned_textgrid/mixins/tiermixins.py +++ b/src/aligned_textgrid/mixins/tiermixins.py @@ -51,7 +51,49 @@ def __add__(self:TierType, new:TierType): new_tier = self.__class__(entries) return new_tier - def append(self, new, re_relate = True): + def append(self, new:'SequenceInterval|SequencePoint', re_relate = True): + """Append a new SequenceInterval or Sequence Point to a tier. + + If the tier is already in a TierGroup, and an appended SequenceInterval + already has a subset_list, or super_instance, these will be appended to the + appropriate tiers above and below. + + Examples: + ```{python} + from aligned_textgrid import SequenceTier, TierGroup, Word, Phone + + word_tier = SequenceTier([ # <1> + Word((0,10, "the")) # <1> + ]) # <1> + phone_tier = SequenceTier([ # <1> + Phone((0,5,"DH")), # <1> + Phone((5,10, "AH0")) # <1> + ]) # <1> + + tier_group = TierGroup([word_tier, phone_tier]) # <2> + + dog = Word((10, 25, "dog")) # <3> + dog.append(Phone((10,15, "D"))) # <3> + dog.append(Phone((15, 20, "AO1"))) # <3> + dog.append(Phone((20,25, "G"))) # <3> + + word_tier.append(dog) # <4> + + print(phone_tier.labels) # <5> + ``` + 1. Creation of Word and Phone tier containing "the" and its phones. + 2. Relating the Word and Phone tiers within a tier group. + 3. Creating a Word for "dog" and appending its phones. + 4. Appending the "dog" word to the Word tier. + 5. The phones of "dog" have been automatically appended to the Phone tier. + + Args: + new (SequenceInterval|SequencePoint): + The SequenceInterval or SequencePoint object to append + re_relate (bool, optional): + If the tier is already within a TierGroup, whether or not + to re-run tier-relation. Defaults to True. + """ if not isinstance(new, SequenceBaseClass): msg = "Only SequenceIntervals or SequencePoints can be appended to a tier." if isinstance(new, TierMixins): @@ -85,6 +127,16 @@ def append(self, new, re_relate = True): def concat(self:TierType, new:TierType): + """Horizontally concatenate a new tier. + + This will horizontally concatenate the `new` tier onto the existing tier. + The time values of `new` will be rightward shifted according to the + end of the original tier. + + Args: + new (TierType): + The tier to concatenate. + """ if not (issubclass(self.entry_class, new.entry_class) or issubclass(new.entry_class, self.entry_class)): raise ValueError("Added tiers must have the same entry class") @@ -151,6 +203,30 @@ def __add__(self:TierGroupType, new:TierGroupType): return new_tg def append(self:TierGroupType, new:TierType): + """Append a new tier to a TierGroup. + + This will add a new tier to a TierGroup + + Examples: + ```{python} + from aligned_textgrid import TierGroup, SequenceTier, Word, Phone + + word_tier = SequenceTier([ + Word((0,10,"the")) + ]) + phone_tier = SequenceTier([ + Phone((0,5,"DH")), + Phone((5,10,"AH0")) + ]) + + tier_group = TierGroup([word_tier]) + tier_group.append(phone_tier) + ``` + + Args: + new (TierType): + A SequenceTier if a TierGroup, or a SequencePointTier if a PointsGroup + """ if not self._seq_type is new._seq_type: raise ValueError(( f"Only a tier of {self._seq_type.__name__} " @@ -200,7 +276,18 @@ def append(self:TierGroupType, new:TierType): pass - def concat(self, new:'TierGroup'): + def concat(self:TierGroupType, new:TierType): + """Horizontally concatenate a tier group. + + The two tier groups must have the same number of tiers and the same + entry classes. All time values of `new` will be rightward shifted + according to the original TierGroup or PointsGroup. + + + Args: + new (TierGroupType): + A TierGroup or PointsGroup to append. + """ self._class_check(new) increment = self.xmax for t1, t2 in zip(self, new): diff --git a/src/aligned_textgrid/mixins/within.py b/src/aligned_textgrid/mixins/within.py index ff36f6f..51ae08e 100644 --- a/src/aligned_textgrid/mixins/within.py +++ b/src/aligned_textgrid/mixins/within.py @@ -10,6 +10,21 @@ Contained = TypeVar("Contained",'SequenceInterval', 'SequenceTier', 'SequencePointTier', 'TierGroup') class WithinMixins: + """A collection of attributes defining within and contains realtions + + Attributes: + within (Within): + The object that the current object is within. + contains (Sequence[Contained]): + A list of objects that the current objects contains + within_index (int): + The current object's index within its' within object. + within_path (int): + The path of indices from the uppermost container + to the current object + id (str): + An id derived from the `within_path`. + """ @property def within(self)->Within: diff --git a/src/aligned_textgrid/points/tiers.py b/src/aligned_textgrid/points/tiers.py index db6e15c..f1c5b34 100644 --- a/src/aligned_textgrid/points/tiers.py +++ b/src/aligned_textgrid/points/tiers.py @@ -23,6 +23,10 @@ class SequencePointTier(Sequence, TierMixins, WithinMixins): """A SequencePointTier class + `SequencePointTier`s have all the same methods and attributes as + [](`~aligned_textgrid.mixins.tiermixins.TierMixins`) and + [](`~aligned_textgrid.mixins.within.WithinMixins`) + Args: tier (list[Point]|list[SequencePoint]|PointTier|Self): A list of SequencePoints, or another SequencePointTier @@ -44,10 +48,8 @@ class SequencePointTier(Sequence, TierMixins, WithinMixins): ```{python} print(point_tier.sequence_list) ``` - + Attributes: - ...: - All attributes and methods included in TierMixins entry_class (Type[SequencePoint]): The class of entries within the tier name (str): @@ -225,15 +227,15 @@ def save_as_tg( class PointsGroup(Sequence, TierGroupMixins, WithinMixins): """A collection of point tiers + `PointsGroup`s have all the same methods and attributes as + [](`~aligned_textgrid.mixins.tiermixins.TierGroupMixins`) and + [](`~aligned_textgrid.mixins.within.WithinMixins`) Args: tiers (list[SequencePointTier]: A list of SequencePointTiers Attributes: - ...: - All attributes and methods availale from TierGroupMixins - entry_classes (list[Type[SequencePointTier],...]): A list of the entry classes """ def __init__( diff --git a/src/aligned_textgrid/sequences/tiers.py b/src/aligned_textgrid/sequences/tiers.py index d9278ef..c21382e 100644 --- a/src/aligned_textgrid/sequences/tiers.py +++ b/src/aligned_textgrid/sequences/tiers.py @@ -28,6 +28,10 @@ class SequenceTier(Sequence, TierMixins, WithinMixins): Given a `praatio` `IntervalTier` or list of `Interval`s, creates `entry_class` instances for every interval. + In addition to the attributes and methods described below, + the attributes and methods from [](`~aligned_textgrid.mixins.tiermixins.TierMixins`) + and [](`~aligned_textgrid.mixins.within.WithinMixins`) are also available. + Args: tier (list[Interval] | list[SequenceInterval] | IntervalTier | Self, optional): A list of interval entries. Defaults to `SequenceList()`. @@ -308,6 +312,10 @@ def save_as_tg( class TierGroup(Sequence,TierGroupMixins, WithinMixins): """Tier Grouping + `PointsGroup`s have all the same methods and attributes as + [](`~aligned_textgrid.mixins.tiermixins.TierGroupMixins`) and + [](`~aligned_textgrid.mixins.within.WithinMixins`) + Args: tiers (list[SequenceTier]): A list of sequence tiers that are meant to be in hierarchical relationships with eachother