Source code for anndata.aio.htk

# -*- coding: UTF-8 -*-
"""
:filename: sppas.src.anndata.aio.htk.py
:author:   Brigitte Bigi
:contact:  develop@sppas.org
:summary:  Input/Output of HTK native file formats.

.. _This file is part of SPPAS: http://www.sppas.org/
..
    -------------------------------------------------------------------------

     ___   __    __    __    ___
    /     |  \  |  \  |  \  /              the automatic
    \__   |__/  |__/  |___| \__             annotation and
       \  |     |     |   |    \             analysis
    ___/  |     |     |   | ___/              of speech

    Copyright (C) 2011-2021  Brigitte Bigi
    Laboratoire Parole et Langage, Aix-en-Provence, France

    Use of this software is governed by the GNU Public License, version 3.

    SPPAS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    SPPAS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with SPPAS. If not, see <http://www.gnu.org/licenses/>.

    This banner notice must not be removed.

    -------------------------------------------------------------------------

The Hidden Markov Model Toolkit (HTK) is a portable toolkit for building
and manipulating hidden Markov models.

The first version of the HTK Hidden Markov Model Toolkit was developed at
the Speech Vision and Robotics Group of the Cambridge University Engineering
Department (CUED) in 1989 by Steve Young.

"""

import codecs

from sppas.src.config import sg

from ..anndataexc import AioMultiTiersError
from ..anndataexc import AioLocationTypeError
from ..ann.annlocation import sppasLocation
from ..ann.annlocation import sppasPoint
from ..ann.annlocation import sppasInterval
from ..ann.annlabel import sppasLabel
from ..ann.annlabel import sppasTag

from .basetrsio import sppasBaseIO
from .aioutils import load
from .aioutils import serialize_labels

# ---------------------------------------------------------------------------

# time values are in multiples of 100ns
TIME_UNIT = pow(10, -7)

# ---------------------------------------------------------------------------


[docs]class sppasBaseHTK(sppasBaseIO): """SPPAS HTK files reader and writer. """
[docs] def __init__(self, name=None): """Initialize a new sppasMLF instance. :param name: (str) This transcription name. """ if name is None: name = self.__class__.__name__ super(sppasBaseHTK, self).__init__(name) self.software = "HTK" self._accept_multi_tiers = False self._accept_no_tiers = True self._accept_metadata = False self._accept_ctrl_vocab = False self._accept_media = False self._accept_hierarchy = False self._accept_point = False self._accept_interval = True self._accept_disjoint = False self._accept_alt_localization = False self._accept_alt_tag = False self._accept_radius = False self._accept_gaps = True self._accept_overlaps = False # to be verified
# -----------------------------------------------------------------------
[docs] @staticmethod def make_point(time_string): """Convert data into the appropriate sppasPoint(). No radius if data is an integer. A default radius of 0.001 if data is a float. :param time_string: (str) a time in HTK format :returns: sppasPoint() representing time in seconds. """ v = float(TIME_UNIT) * float(time_string) return sppasPoint(v, radius=0.005)
# ----------------------------------------------------------------------- @staticmethod def _format_time(second_count): """Convert a time in seconds into HTK format.""" return int(1. / TIME_UNIT * float(second_count)) # ----------------------------------------------------------------------- @staticmethod def _serialize_annotation(ann): """Convert an annotation into a line for HTK lab of mlf files. :param ann: (sppasAnnotation) :returns: (str) """ text = serialize_labels(ann.get_labels(), separator=" ", empty="", alt=False) # no label defined, or empty label if len(text) == 0: return "" if ann.get_location().is_point(): raise AioLocationTypeError('HTK Label', 'points') begin = sppasBaseHTK._format_time( ann.get_lowest_localization().get_midpoint()) end = sppasBaseHTK._format_time( ann.get_highest_localization().get_midpoint()) if ' ' not in text: location = "{:d} {:d}".format(begin, end) else: # one "token" at a line, and only begin at first location = "{:d}".format(begin) text = text.replace(' ', '\n') return "{:s} {:s}\n".format(location, text)
# ---------------------------------------------------------------------------
[docs]class sppasLab(sppasBaseHTK): """SPPAS LAB reader and writer. Each line of a HTK label file contains the actual label optionally preceded by start and end times, and optionally followed by a match score. [<start> <end>] <<name> [<score>]> [";" <comment>] Multiple alternatives are written as a sequence of separate label lists separated by three slashes (///). Examples: - simple transcription: 0000000 3600000 ice 3600000 8200000 cream - alternative labels: 0000000 2200000 I 2200000 8200000 scream /// 0000000 3600000 ice 3600000 8200000 cream /// 0000000 3600000 eyes 3600000 8200000 cream ************* Only simple transcription is implemented yet. *********** """
[docs] @staticmethod def detect(filename): """Check whether a file is of HTK-Lab format or not. :param filename: (str) Name of the file to check. :returns: (bool) """ try: with codecs.open(filename, 'r', sg.__encoding__) as fp: line = fp.readline() fp.close() except IOError: return False except UnicodeDecodeError: return False # the first line contains at least 2 columns tab = line.split() if len(tab) < 2: return False # First column is the start time: an integer try: int(line[0]) except ValueError: return False return sppasBaseIO.is_number(line[0])
# -----------------------------------------------------------------------
[docs] def __init__(self, name=None): """Initialize a new sppasLab instance. :param name: (str) This transcription name. """ if name is None: name = self.__class__.__name__ super(sppasLab, self).__init__(name) self.default_extension = "lab"
# -----------------------------------------------------------------------
[docs] def read(self, filename): """Read a transcription from a file. :param filename: """ lines = load(filename) tier = self.create_tier('Trans-MLF') text = "" prev_end = sppasBaseHTK.make_point(0) for line in lines: line = line.strip().split() has_begin = len(line) > 0 and sppasBaseIO.is_number(line[0]) has_end = len(line) > 1 and sppasBaseIO.is_number(line[1]) if has_begin and has_end: if len(text) > 0: time = sppasInterval(prev_end, sppasBaseHTK.make_point(line[0])) tier.create_annotation(sppasLocation(time), sppasLabel(sppasTag(text))) time = sppasInterval(sppasBaseHTK.make_point(line[0]), sppasBaseHTK.make_point(line[1])) text = line[2] score = None if len(line) > 3: try: score = float(line[3]) except ValueError: # todo: auxiliary labels or comment pass tier.create_annotation(sppasLocation(time), sppasLabel(sppasTag(text), score)) text = "" prev_end = sppasBaseHTK.make_point(line[1]) elif has_begin: text = text + " " + " ".join(line[1]) # todo: auxiliary labels or comment else: text = text + " " + " ".join(line)
# -----------------------------------------------------------------------
[docs] def write(self, filename): """Write a transcription into a file. :param filename: (str) """ if len(self) != 1: raise AioMultiTiersError("HTK Label") with codecs.open(filename, 'w', sg.__encoding__) as fp: if self.is_empty() is False: for ann in self[0]: content = sppasBaseHTK._serialize_annotation(ann) if len(content) > 0: fp.write(content) fp.close()