Skip to content

Commit

Permalink
MRG: #232 from vocalpy/remove-segment-from-row
Browse files Browse the repository at this point in the history
CLN: Remove unused Segment.from_row method, fix #231
  • Loading branch information
NickleDave committed Mar 6, 2023
2 parents f95b08a + 150d011 commit f027b89
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 116 deletions.
95 changes: 39 additions & 56 deletions src/crowsetta/segment.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
"""A class that represents a segment of a time series,
used to annotate animal communication."""
import warnings

import attr


def float_or_None(val):
"""converter that casts val to float, or returns None if val is string 'None'"""
if val == "None":
return None
else:
return float(val)


def int_or_None(val):
"""converter that casts val to int, or returns None if val is string 'None'"""
if val == "None":
return None
import numpy as np
from attr.validators import instance_of


def int64_to_int(val):
"""Converter that converts ``numpy.int64`` to ``int``,
returns ``int`` as is, and errors for other values.
"""
if hasattr(val, "dtype"):
if val.dtype == np.int64:
return int(val)
elif isinstance(val, int):
return val
else:
return int(val)
raise TypeError(f"Invalid type {type(val)} for onset or offset sample: {val}. Must be an integer.")


@attr.s(frozen=True)
Expand All @@ -32,51 +29,37 @@ class Segment(object):
_FIELDS = ("label", "onset_s", "offset_s", "onset_sample", "offset_sample")

label = attr.ib(converter=str)
onset_s = attr.ib(converter=attr.converters.optional(float_or_None))
offset_s = attr.ib(converter=attr.converters.optional(float_or_None))
onset_sample = attr.ib(converter=attr.converters.optional(int_or_None))
offset_sample = attr.ib(converter=attr.converters.optional(int_or_None))
onset_s = attr.ib(validator=attr.validators.optional(instance_of(float)), default=None)
offset_s = attr.ib(validator=attr.validators.optional(instance_of(float)), default=None)
onset_sample = attr.ib(
validator=attr.validators.optional(instance_of(int)),
converter=attr.converters.optional(int64_to_int),
default=None,
)
offset_sample = attr.ib(
validator=attr.validators.optional(instance_of(int)),
converter=attr.converters.optional(int64_to_int),
default=None,
)
asdict = attr.asdict

@classmethod
def from_row(cls, row, header=None):
if type(row) is list:
if header is None:
raise ValueError("must provide header when row is a list")
row = dict(zip(header, row))
elif type(row) is dict:
if header is not None:
warnings.warn(
"Type of 'row' argument was 'dict' but 'header'"
"argument was not None. Value for header will not "
"be used because keys of dict are used as field from "
"header. To use a different header, convert row to a list: "
">>> row = list(dict.values())"
)

row_dict = {}
for field in cls._FIELDS:
try:
row_dict[field] = row[field]
except KeyError:
raise KeyError(f"missing field {field} in row")
def __attrs_post_init__(self):
if (self.onset_sample is None and self.offset_sample is None) and (
self.onset_s is None and self.offset_s is None
):
raise ValueError("must provide either onset_sample and offset_sample, or " "onsets_s and offsets_s")

return cls.from_keyword(**row_dict)
if self.onset_sample and self.offset_sample is None:
raise ValueError(f"onset_sample specified as {self.onset_sample} but offset_sample is None")
if self.onset_sample is None and self.offset_sample:
raise ValueError(f"offset_sample specified as {self.offset_sample} but onset_sample is None")
if self.onset_s and self.offset_s is None:
raise ValueError(f"onset_s specified as {self.onset_s} but offset_s is None")
if self.onset_s is None and self.offset_s:
raise ValueError(f"offset_s specified as {self.offset_sample} but onset_s is None")

@classmethod
def from_keyword(cls, label, onset_s=None, offset_s=None, onset_sample=None, offset_sample=None):
if (onset_sample is None and offset_sample is None) and (onset_s is None and offset_s is None):
raise ValueError("must provide either onset_sample and offset_sample, or " "onsets_s and offsets_s")

if onset_sample and offset_sample is None:
raise ValueError(f"onset_sample specified as {onset_sample} but offset_sample is None")
if onset_sample is None and offset_sample:
raise ValueError(f"offset_sample specified as {offset_sample} but onset_sample is None")
if onset_s and offset_s is None:
raise ValueError(f"onset_s specified as {onset_s} but offset_s is None")
if onset_s is None and offset_s:
raise ValueError(f"offset_s specified as {offset_sample} but onset_s is None")

return cls(
label=label, onset_s=onset_s, offset_s=offset_s, onset_sample=onset_sample, offset_sample=offset_sample
)
95 changes: 35 additions & 60 deletions tests/test_segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,38 @@

from crowsetta.segment import Segment


def test_Segment_init_onset_offset_in_seconds_from_keyword():
a_segment = Segment.from_keyword(label="a", onset_s=0.123, offset_s=0.170)
for attr in ["label", "onset_s", "offset_s"]:
assert hasattr(a_segment, attr)
for attr in ["onset_sample", "offset_sample"]:
assert getattr(a_segment, attr) is None


def test_Segment_init_onset_offset_in_Hertz_from_keyword():
a_segment = Segment.from_keyword(label="a", onset_sample=15655, offset_sample=20001)
for attr in ["label", "onset_sample", "offset_sample"]:
assert hasattr(a_segment, attr)
for attr in ["onset_s", "offset_s"]:
assert getattr(a_segment, attr) is None


def test_Segment_init_onset_offset_in_seconds_from_row():
header = ["label", "onset_s", "offset_s", "onset_sample", "offset_sample"]
row = ["a", "0.123", "0.170", "None", "None"]
a_segment = Segment.from_row(row=row, header=header)
for attr in ["label", "onset_s", "offset_s"]:
assert hasattr(a_segment, attr)
for attr in ["onset_sample", "offset_sample"]:
assert getattr(a_segment, attr) is None


def test_Segment_init_onset_offset_in_Hertz_from_row():
header = ["label", "onset_s", "offset_s", "onset_sample", "offset_sample"]
row = ["a", "None", "None", "15655", "20001"]
a_segment = Segment.from_row(row=row, header=header)
for attr in ["label", "onset_sample", "offset_sample"]:
assert hasattr(a_segment, attr)
for attr in ["onset_s", "offset_s"]:
assert getattr(a_segment, attr) is None


def test_Segment_init_missing_onsets_and_offsets_raises():
with pytest.raises(ValueError):
a_segment = Segment.from_keyword(label="a")


def test_Segment_init_missing_offset_seconds_raises():
with pytest.raises(ValueError):
a_segment = Segment.from_keyword(label="a", onset_s=0.123)


def test_Segment_init_missing_onset_seconds_raises():
with pytest.raises(ValueError):
a_segment = Segment.from_keyword(label="a", offset_s=0.177)


def test_Segment_init_missing_offset_Hertz_raises():
with pytest.raises(ValueError):
a_segment = Segment.from_keyword(label="a", onset_sample=0.123)


def test_Segment_init_missing_onset_Hertz_raises():
with pytest.raises(ValueError):
a_segment = Segment.from_keyword(label="a", offset_sample=0.177)
@pytest.mark.parametrize(
'kwargs',
[
dict(label="a", onset_s=0.123, offset_s=0.170),
dict(label="a", onset_sample=15655, offset_sample=20001),
]
)
def test_Segment_from_keyword(kwargs):
a_segment = Segment.from_keyword(**kwargs)
for attr in ["label", "onset_s", "offset_s", "onset_sample", "offset_sample"]:
if attr in kwargs:
assert hasattr(a_segment, attr)
assert getattr(a_segment, attr) == kwargs[attr]
else:
assert getattr(a_segment, attr) is None


@pytest.mark.parametrize(
'kwargs, expected_error',
[
(dict(label="a",), ValueError),
(dict(label="a", onset_s=0.123), ValueError),
(dict(label="a", offset_s=0.177), ValueError),
(dict(label="a", onset_sample=15655), ValueError),
(dict(label="a", offset_sample=20001), ValueError),
(dict(label="a", onset_s=15655), TypeError),
(dict(label="a", offset_s=20001), TypeError),
(dict(label="a", onset_sample=0.123), TypeError),
(dict(label="a", offset_sample=0.177), TypeError),
]
)
def test_Segment_from_keyword_raises(kwargs, expected_error):
with pytest.raises(expected_error):
Segment.from_keyword(**kwargs)

0 comments on commit f027b89

Please sign in to comment.