Skip to content

Commit

Permalink
Merge pull request #193 from Forced-Alignment-and-Vowel-Extraction/co…
Browse files Browse the repository at this point in the history
…ncat-methods

Concat, Append and Add methods
  • Loading branch information
JoFrhwld authored Jun 24, 2024
2 parents 35e03a3 + 079cef2 commit f789a1e
Show file tree
Hide file tree
Showing 23 changed files with 2,238 additions and 275 deletions.
Empty file.
55 changes: 55 additions & 0 deletions doc_src/reference/TierGroup.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# TierGroup { #aligned_textgrid.sequences.tiers.TierGroup }

`sequences.tiers.TierGroup(self, tiers=[SequenceTier()])`

Tier Grouping

## Parameters

| Name | Type | Description | Default |
|---------|----------------------|---------------------------------------------------------------------------------------------|--------------------|
| `tiers` | list\[SequenceTier\] | A list of sequence tiers that are meant to be in hierarchical relationships with eachother | `[SequenceTier()]` |

## Attributes

| Name | Type | Description |
|---------------|----------------------------------|------------------------------------------------|
| tier_list | list\[SequenceTier\] | List of sequence tiers that have been related. |
| entry_classes | list\[Type\[SequenceInterval\]\] | A list of the entry classes for each tier. |
| tier_names | list\[str\] | A list of tier names |
| xmax | float | Maximum time |
| xmin | float | Minimum time |
| \[\] | | Indexable. Returns a SequenceTier |

## Methods

| Name | Description |
| --- | --- |
| [get_intervals_at_time](#aligned_textgrid.sequences.tiers.TierGroup.get_intervals_at_time) | Get intervals at time |
| [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 }

`sequences.tiers.TierGroup.get_intervals_at_time(time)`

Get intervals at time

Returns a list of intervals at `time` for each tier.

#### Parameters

| Name | Type | Description | Default |
|--------|--------|-------------------|------------|
| `time` | float | Time in intervals | _required_ |

#### Returns

| Type | Description |
|-------------|--------------------------------------------------------------|
| list\[int\] | A list of interval indices, one for each tier in `tier_list` |

### show_structure { #aligned_textgrid.sequences.tiers.TierGroup.show_structure }

`sequences.tiers.TierGroup.show_structure()`

Show the hierarchical structure
10 changes: 9 additions & 1 deletion docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ format:
light: flatly
dark: darkly
toc: true
code-annotations: hover

# tell quarto to read the generated sidebar
metadata-files:
Expand Down Expand Up @@ -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
Expand All @@ -121,7 +124,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
Expand All @@ -143,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:
Expand Down
2 changes: 1 addition & 1 deletion docs/objects.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions src/aligned_textgrid/__init__.py
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
153 changes: 148 additions & 5 deletions src/aligned_textgrid/aligned_textgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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, [])
Expand Down Expand Up @@ -329,23 +356,139 @@ 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]
tier.__init__(entries)

tier_group.__init__(tier_group)

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)->None:
"""Cleanup gaps in AlignedTextGrid
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.
"""
with warnings.catch_warnings():
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])

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_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
):
"""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,
Expand Down
Loading

0 comments on commit f789a1e

Please sign in to comment.