Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for popping a class tier. #187

Merged
merged 6 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 112 additions & 5 deletions src/aligned_textgrid/aligned_textgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,28 +335,50 @@ def xmax(self):
def interleave_class(
self,
name:str,
above:SequenceInterval|str = None,
below:SequenceInterval|str = None,
above:Type[SequenceInterval]|str = None,
below:Type[SequenceInterval]|str = None,
timing_from: Literal["above", "below"] = "below",
copy_labels: bool = True
):
"""Interleave a new entry class.

You can set either `above` or `below`, but not both.

Args:
name (str):
Name of the new class
above (SequenceInterval|str, optional):
above (Type[SequenceInterval]|str, optional):
Which entry class to interleave above.
below (SequenceInterval|str, optional):
below (Type[SequenceInterval]|str, optional):
Which entry class to interleave below.
timing_from (Literal['above', 'below'], optional):
Which tier to draw timing from. Defaults to "below".
copy_labels (bool):
Whether or not to copy labels from the tier providing
timing information. Defaults to True.

Examples:
```{python}
from aligned_textgrid import AlignedTextGrid, Word, Phone

atg = AlignedTextGrid(
textgrid_path = "../usage/resources/josef-fruehwald_speaker.TextGrid",
entry_classes = [Word, Phone]
)
print(atg)
```

```{python}
atg.interleave_class(
name = "Syllable",
above = "Phone",
timing_from = "below",
copy_labels = True
)
print(atg)
```

You can set either `above` or `below`, but not both.

"""

if above and below:
Expand Down Expand Up @@ -439,6 +461,91 @@ def interleave_class(
tgr.within = self
self.contains = self.tier_groups

def pop_class(
self,
name: Type[SequenceInterval]|str
):
"""Pop a class from an AlignedTextGrid

Remove a class of tiers from an AlignedTextGrid

Args:
name (Type[SequenceInterval] | str):
The tier class to remove.

Examples:
```{python}
from aligned_textgrid import AlignedTextGrid, custom_classes

atg = AlignedTextGrid(
textgrid_path = "../usage/resources/spritely.TextGrid",
entry_classes = custom_classes([
"PrWord",
"Foot",
"Syl",
"OnsetRime",
"SylPart",
"Phone"
])
)
print(atg)
```

```{python}
atg.pop_class("SylPart")
print(atg)
```
"""

if type(name) is type:
name = name.__name__

new_tier_groups = []
for tg in self.tier_groups:
entry_classes = [t.entry_class.__name__ for t in tg]

if not name in entry_classes:
new_tier_groups.append(tg)
continue

if name in entry_classes and len(entry_classes) == 1:
warnings.warn(
(
f"TierGroup {tg.name} contained only {name} tier "
"so it was removed."
)
)
continue

pop_tier, = [t for t in tg if t.entry_class.__name__ == name]
keep_tiers = [t for t in tg if t.entry_class.__name__ != name]

pop_tier_super = pop_tier.entry_class.superset_class
pop_tier_sub = pop_tier.entry_class.subset_class

if not isinstance(pop_tier_super, Top):
pop_tier_super.set_subset_class(pop_tier_sub)

if not isinstance(pop_tier_sub, Bottom):
pop_tier_sub.set_superset_class(pop_tier_super)

for tier in keep_tiers:
tier.superset_class = tier.entry_class.superset_class
tier.subset_class = tier.entry_class.subset_class
new_tg = TierGroup(keep_tiers)
for seq in new_tg[-1]:
seq.subset_list = []
seq.contains = []
new_tg.name = tg.name
new_tier_groups.append(new_tg)

self.tier_groups = new_tier_groups
new_entry_classes = [tg.entry_classes for tg in self.tier_groups]
self.entry_classes = new_entry_classes
for tgr in self.tier_groups:
tgr.within = self
self.contains = self.tier_groups

def get_class_by_name(
self,
class_name: str
Expand Down
1 change: 1 addition & 0 deletions src/aligned_textgrid/points/tiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def __init__(
):
self.tier_list = tiers
self.contains = self.tier_list
self._name = self.make_name()

def __getitem__(
self,
Expand Down
30 changes: 29 additions & 1 deletion src/aligned_textgrid/polar/polar_grid.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from aligned_textgrid.aligned_textgrid import AlignedTextGrid
from aligned_textgrid.polar.polar_classes import PrStr, ToBI, \
TurningPoints, Ranges, Levels, Misc
from aligned_textgrid.sequences.word_and_phone import Word, Phone
from praatio import textgrid

class PolarGrid(AlignedTextGrid):
Expand Down Expand Up @@ -32,6 +35,7 @@ def __init__(self,
self._set_named_accessors()
self._relate_levels_and_ranges()
self._relate_levels_and_points()
self._name_groups()

def _set_named_accessors(self):
for tg in self.tier_groups:
Expand All @@ -46,4 +50,28 @@ def _relate_levels_and_ranges(self):

def _relate_levels_and_points(self):
for l in self.Levels:
l.set_turning_point(self.TurningPoints)
l.set_turning_point(self.TurningPoints)

def _name_groups(self):
wp_classes = [Word, Phone]
p_classes = [
PrStr,
ToBI,
TurningPoints,
Ranges,
Levels,
Misc
]
range_class = [Ranges]

for group in self:
entry_classes = group.entry_classes
if any([issubclass(cl, ecl) for cl in entry_classes for ecl in wp_classes]):
print("yes")
group.name = "Word_Phone"
if any([issubclass(cl, ecl) for cl in entry_classes for ecl in p_classes]):
group.name = "Points"
if any([issubclass(cl, ecl) for cl in entry_classes for ecl in range_class]):
group.name = "Ranges"


47 changes: 47 additions & 0 deletions tests/test_aligned_textgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,53 @@ def test_exceptions(self):
timing_from="Word"
)

class TestPop:

Turns, Word,Phone = custom_classes(["Turns", "Word", "Phone"])
atg = AlignedTextGrid(
textgrid_path="tests/test_data/KY25A_1_multi.TextGrid",
entry_classes=[Word, Phone, Turns]
)
turn_lens = [
len(x.contains)
for tg in atg
for x in tg.Turns
if len(x.label) > 0
]

def test_pre_pop(self):
for tg in self.atg:
entry_class_names = [x.__name__ for x in tg.entry_classes]
assert "Word" in entry_class_names

def test_run_pop(self):
self.atg.pop_class(Word)

for tg in self.atg:
entry_class_names = [x.__name__ for x in tg.entry_classes]
assert "Word" not in entry_class_names

def test_pop_result(self):
self.new_turn_lens = [
len(x.contains)
for tg in self.atg
for x in tg.Turns
if len(x.label) > 0
]

for old, new in zip(self.turn_lens, self.new_turn_lens):
assert new > old

def test_no_pop(self):
assert len(self.atg[0]) == 2
self.atg.pop_class("NotInAtg")
assert len(self.atg[0]) == 2

def test_all_pop(self):
self.atg.pop_class("Turns")
with pytest.warns():
self.atg.pop_class("Phone")

class TestClassCloning:

def test_class_clone(self):
Expand Down
14 changes: 14 additions & 0 deletions tests/test_polar/test_polar_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ def test_tier_classes(self):
class_check_3 = [isinstance(x, SequenceTier) for x in self.ptg[2]]
assert all(class_check_3)

def test_ptg_indexing(self):
start = self.ptg.Word_Phone.xmin
end = self.ptg.Word_Phone.xmax
mid = ((end-start)/2)+start

interval_idx = self.ptg.get_intervals_at_time(mid)
results = self.ptg[interval_idx]

assert len(interval_idx) == len(self.ptg)
for int_gr, gr, res in zip(interval_idx, self.ptg, results):
assert len(int_gr) == len(gr) == len(res)



class TestPolarClasses:
entry_classes = [
[Word, Phone],
Expand Down
Loading