Skip to content

Commit

Permalink
Merge pull request #191 from Forced-Alignment-and-Vowel-Extraction/at…
Browse files Browse the repository at this point in the history
…g-class-input

Flexibilization of ATG object creation
  • Loading branch information
JoFrhwld authored Jun 15, 2024
2 parents 4c9a41c + 0cc7eb1 commit 35e03a3
Show file tree
Hide file tree
Showing 12 changed files with 468 additions and 84 deletions.
12 changes: 0 additions & 12 deletions docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,6 @@ website:
- usage/05_Outputs/00_to_textgrid.ipynb
- usage/05_Outputs/01_to_dataframe.ipynb
- usage/05_Outputs/02_pickling.ipynb
# - section: Processing patterns
# contents:
# - getting-started/single-file.qmd
# - getting-started/directory.qmd
# - section: Customizing a recoding scheme
# contents:
# - getting-started/rule-scheme-basics.qmd
# - getting-started/rule-application.qmd
# - section: Details
# contents:
# - getting-started/condition-attributes.qmd
# - getting-started/condition-relations.qmd

format:
html:
Expand Down
38 changes: 24 additions & 14 deletions src/aligned_textgrid/aligned_textgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@
from copy import copy
import numpy as np
from collections.abc import Sequence
from pathlib import Path
import warnings


class AlignedTextGrid(Sequence, WithinMixins):
"""An aligned Textgrid
Args:
textgrid (Textgrid, optional): A `praatio` TextGrid
textgrid_path (str, optional): A path to a TextGrid file to be
read in with `praatio.textgrid.openTextgrid`
textgrid (str|Path|praatio.textgrid.Textgrid, optional): A `praatio` TextGrid
entry_classes (Sequence[Sequence[Type[SequenceInterval]]] | Sequence[Type[SequenceInterval]], optional):
If a single list of `SequenceInterval` subclasses is given, they will be
repeated as many times as necessary to assign a class to every tier.
Expand Down Expand Up @@ -54,23 +53,20 @@ class for each tier within each tier group. Say, if only the first speaker

def __init__(
self,
textgrid: Textgrid = None,
textgrid_path: str = None,
textgrid: Textgrid|str|Path = None,
entry_classes:
Sequence[Sequence[Type[SequenceInterval]]] |
Sequence[Type[SequenceInterval]]
= [SequenceInterval]
= [SequenceInterval],
*,
textgrid_path: str = None
):
self.entry_classes = self._reclone_classes(entry_classes)
if textgrid_path:
textgrid = textgrid_path

if textgrid:
self.tg_tiers, self.entry_classes = self._nestify_tiers(textgrid, self.entry_classes)
elif textgrid_path:
tg = openTextgrid(
fnFullPath=textgrid_path,
includeEmptyIntervals=True,
duplicateNamesMode='rename'
)
self.tg_tiers, self.entry_classes = self._nestify_tiers(tg, self.entry_classes)
self._process_textgrid_arg(textgrid)
else:
warnings.warn('Initializing an empty AlignedTextGrid')
self._init_empty()
Expand Down Expand Up @@ -113,6 +109,20 @@ def __repr__(self):
def __setstate__(self, d):
self.__dict__ = d

def _process_textgrid_arg(self, arg):
if isinstance(arg, str) or isinstance(arg, Path):
arg_str = str(arg)
tg = openTextgrid(
fnFullPath=arg_str,
includeEmptyIntervals=True,
duplicateNamesMode='rename'
)

if isinstance(arg, Textgrid):
tg = arg

self.tg_tiers, self.entry_classes = self._nestify_tiers(tg, self.entry_classes)

def _extend_classes(
self,
tg: Textgrid,
Expand Down
60 changes: 49 additions & 11 deletions src/aligned_textgrid/points/points.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,44 @@
from aligned_textgrid.mixins.within import WithinMixins
from aligned_textgrid.sequences.tiers import SequenceTier
from aligned_textgrid.sequences.sequences import SequenceInterval
from typing_extensions import Self
import warnings
import numpy as np

from typing import Union
import sys
if sys.version_info >= (3,11):
from typing import Self
else:
from typing_extensions import Self


class SequencePoint(PrecedenceMixins, InTierMixins, WithinMixins):
"""Sequence Points
Args:
point (Point): a `praatio.point` object
point (list|tuple|Point|Self):
A list or tuple of a time and label value.
Examples:
```{python}
from aligned_textgrid import SequencePoint, SequenceInterval
first_point = SequencePoint((0, "first"))
print(first_point)
```
```{python}
second_point = SequencePoint((1, "second"))
interval = SequenceInterval((0.5, 2, "word"))
print(
first_point.distance_from(second_point)
)
print(
first_point.distance_from(interval)
)
```
Attributes:
...:
All attributes and methods included in PrecedenceMixins and InTierMixins
Expand All @@ -38,12 +64,20 @@ class SequencePoint(PrecedenceMixins, InTierMixins, WithinMixins):

def __init__(
self,
point = Point(0, "")
point: list|tuple|Point|Self = (0, "")
):
super().__init__()
if not point:
point = Point(None, None)


if isinstance(point, SequencePoint):
point = (point.time, point.label)

if len(point) > 2:
raise ValueError((
"The tuple to create a SequencePoint should be "
"no more than 2 values long. "
f"{len(point)} were provided."
))
point = Point(*point)

self.time = point.time
self.label = point.label

Expand Down Expand Up @@ -75,6 +109,10 @@ def __getitem__(self, idex):
return None

## Properties
@property
def entry_class(self):
return self.__class__

@property
def fol_distance(self):
if self.fol and self.fol.time:
Expand Down Expand Up @@ -105,7 +143,7 @@ def prev_distance(self):
def distance_from(
self,
entry: Self|SequenceInterval
) -> Union[float, np.array]:
) -> np.array:
"""Distance from an entry
Args:
Expand All @@ -114,12 +152,12 @@ def distance_from(
point from
Returns:
(float | np.array):
(np.array):
a single value in the case of a point, a numpy array in
the case of an interval.
"""
if isinstance(entry, SequencePoint):
return self.time - entry.time
return np.array(self.time - entry.time)

if isinstance(entry, SequenceInterval):
entry_times = np.array([entry.start, entry.end])
Expand Down
61 changes: 51 additions & 10 deletions src/aligned_textgrid/points/tiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,39 @@
import numpy as np
from typing import Type
from collections.abc import Sequence

import sys
if sys.version_info >= (3,11):
from typing import Self
else:
from typing_extensions import Self

import warnings

class SequencePointTier(Sequence, TierMixins, WithinMixins):
"""A SequencePointTier class
Args:
tier (PointTier | list[Point]):
Either a `praatio` PointTier or a list of `praatio` Points
tier (list[Point]|list[SequencePoint]|PointTier|Self):
A list of SequencePoints, or another SequencePointTier
entry_class (Type[SequencePoint]):
A SequencePoint subclass
Examples:
```{python}
from aligned_textgrid import SequencePoint, SequencePointTier
point_a = SequencePoint((0,"a"))
point_b = SequencePoint((1, "b"))
point_tier = SequencePointTier([point_a, point_b])
print(point_tier)
```
```{python}
print(point_tier.sequence_list)
```
Attributes:
...:
Expand All @@ -42,18 +65,36 @@ class SequencePointTier(Sequence, TierMixins, WithinMixins):
"""
def __init__(
self,
tier:PointTier|list[Point] = [],
entry_class:Type[SequencePoint] = SequencePoint
tier:list[Point]|list[SequencePoint]|PointTier|Self = [],
entry_class:Type[SequencePoint] = None
):
to_check = tier
if isinstance(tier, PointTier):
self.entry_list = tier.entries
self.name = tier.name
else:
self.entry_list = tier
self.name = entry_class.__name__
to_check = tier.entries

has_class = any([hasattr(x, "entry_class") for x in to_check])

self.entry_class = entry_class
if not entry_class and has_class:
entry_class = tier[0].entry_class

if not entry_class and not has_class:
entry_class = SequencePoint

name = entry_class.__name__
entries = tier

if isinstance(tier, PointTier):
entries = tier.entries
name = tier.name

if isinstance(tier, SequencePointTier):
entries = tier.entry_list
if tier.name != tier.entry_class.__name__:
name = tier.name

self.entry_list = entries
self.entry_class = entry_class
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 = []
Expand Down
Loading

0 comments on commit 35e03a3

Please sign in to comment.