mmif-python 1.0.12__tar.gz → 1.0.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mmif-python-1.0.12/mmif_python.egg-info → mmif-python-1.0.14}/PKG-INFO +1 -1
- mmif-python-1.0.14/VERSION +1 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/serialize/annotation.py +34 -15
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/serialize/mmif.py +40 -17
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/serialize/model.py +26 -19
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/serialize/view.py +39 -1
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/utils/timeunit_helper.py +4 -4
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/utils/video_document_helper.py +10 -8
- mmif-python-1.0.14/mmif/ver/__init__.py +2 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14/mmif_python.egg-info}/PKG-INFO +1 -1
- mmif-python-1.0.12/VERSION +0 -1
- mmif-python-1.0.12/mmif/ver/__init__.py +0 -2
- {mmif-python-1.0.12 → mmif-python-1.0.14}/LICENSE +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/MANIFEST.in +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/README.md +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/__init__.py +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/res/__init__.py +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/res/clams.vocabulary.yaml +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/res/do-not-edit.txt +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/res/mmif.json +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/serialize/__init__.py +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/utils/__init__.py +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/utils/sequence_helper.py +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/ver/do-not-edit.txt +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/vocabulary/__init__.py +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/vocabulary/annotation_types.py +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/vocabulary/base_types.py +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/vocabulary/do-not-edit.txt +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif/vocabulary/document_types.py +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif_docloc_http/__init__.py +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif_python.egg-info/SOURCES.txt +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif_python.egg-info/dependency_links.txt +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif_python.egg-info/requires.txt +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/mmif_python.egg-info/top_level.txt +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/requirements.cv +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/requirements.seq +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/requirements.txt +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/setup.cfg +0 -0
- {mmif-python-1.0.12 → mmif-python-1.0.14}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mmif-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.14
|
|
4
4
|
Summary: Python implementation of MultiMedia Interchange Format specification. (https://mmif.clams.ai)
|
|
5
5
|
Home-page: https://mmif.clams.ai
|
|
6
6
|
Author: Brandeis Lab for Linguistics and Computation
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.14
|
|
@@ -26,7 +26,7 @@ __all__ = ['Annotation', 'AnnotationProperties', 'Document', 'DocumentProperties
|
|
|
26
26
|
|
|
27
27
|
T = TypeVar('T')
|
|
28
28
|
LIST_PRMTV = typing.List[PRMTV_TYPES] # list of values (most cases for annotation props)
|
|
29
|
-
LIST_LIST_PRMTV = typing.List[LIST_PRMTV] # list of
|
|
29
|
+
LIST_LIST_PRMTV = typing.List[LIST_PRMTV] # list of lists of values (e.g. for coordinates)
|
|
30
30
|
DICT_PRMTV = typing.Dict[str, PRMTV_TYPES] # dict of values (`text` prop of `TextDocument` and other complex props)
|
|
31
31
|
DICT_LIST_PRMTV = typing.Dict[str, LIST_PRMTV] # dict of list of values (even more complex props)
|
|
32
32
|
|
|
@@ -127,6 +127,20 @@ class Annotation(MmifObject):
|
|
|
127
127
|
def id(self, aid: str) -> None:
|
|
128
128
|
self.properties.id = aid
|
|
129
129
|
|
|
130
|
+
@property
|
|
131
|
+
def long_id(self) -> str:
|
|
132
|
+
if self.parent is not None and len(self.parent) > 0:
|
|
133
|
+
return f"{self.parent}{self.id_delimiter}{self.id}"
|
|
134
|
+
else:
|
|
135
|
+
return self.id
|
|
136
|
+
|
|
137
|
+
@long_id.setter
|
|
138
|
+
def long_id(self, long_id: str) -> None:
|
|
139
|
+
if self.id_delimiter in long_id:
|
|
140
|
+
self.parent, self.id = long_id.split(self.id_delimiter)
|
|
141
|
+
else:
|
|
142
|
+
self.id = long_id
|
|
143
|
+
|
|
130
144
|
@staticmethod
|
|
131
145
|
def check_prop_value_is_simple_enough(
|
|
132
146
|
value: Union[PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]) -> bool:
|
|
@@ -401,26 +415,31 @@ class AnnotationProperties(MmifObject, MutableMapping[str, T]):
|
|
|
401
415
|
|
|
402
416
|
def __iter__(self) -> Iterator[str]:
|
|
403
417
|
"""
|
|
404
|
-
``__iter__`` on Mapping should basically work as ``keys()`` method
|
|
405
|
-
|
|
406
|
-
empty props are ignored (note that emtpy but required props are serialized
|
|
407
|
-
with the *emtpy* value).
|
|
408
|
-
Hence, this ``__iter__`` method should also work in the same way and
|
|
409
|
-
ignore empty optional props.
|
|
418
|
+
``__iter__`` on Mapping should basically work as ``keys()`` method
|
|
419
|
+
of vanilla dict.
|
|
410
420
|
"""
|
|
411
421
|
for key in itertools.chain(self._named_attributes(), self._unnamed_attributes):
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
422
|
+
yield key
|
|
423
|
+
|
|
424
|
+
def __getitem__(self, key):
|
|
425
|
+
"""
|
|
426
|
+
Parent MmifObject class has a __getitem__ method that checks if
|
|
427
|
+
the value is empty when asked for an unnamed attribute. But for
|
|
428
|
+
AnnotationProperties, any arbitrary property that's added
|
|
429
|
+
explicitly by the user (developer) should not be ignored and
|
|
430
|
+
returned even the value is empty.
|
|
431
|
+
"""
|
|
432
|
+
if key in self._named_attributes():
|
|
433
|
+
return self.__dict__[key]
|
|
434
|
+
else:
|
|
435
|
+
return self._unnamed_attributes[key]
|
|
420
436
|
|
|
421
437
|
def __init__(self, mmif_obj: Optional[Union[bytes, str, dict]] = None) -> None:
|
|
422
438
|
self.id: str = ''
|
|
439
|
+
# any individual at_type (subclassing this class) can have its own set of required attributes
|
|
423
440
|
self._required_attributes = ["id"]
|
|
441
|
+
# allowing additional attributes for arbitrary annotation properties
|
|
442
|
+
self._unnamed_attributes = {}
|
|
424
443
|
super().__init__(mmif_obj)
|
|
425
444
|
|
|
426
445
|
|
|
@@ -10,13 +10,13 @@ import math
|
|
|
10
10
|
import warnings
|
|
11
11
|
from collections import defaultdict
|
|
12
12
|
from datetime import datetime
|
|
13
|
-
from typing import List, Union, Optional, Dict,
|
|
13
|
+
from typing import List, Union, Optional, Dict, cast
|
|
14
14
|
|
|
15
15
|
import jsonschema.validators
|
|
16
|
+
|
|
16
17
|
import mmif
|
|
17
18
|
from mmif import ThingTypesBase
|
|
18
19
|
from mmif.vocabulary import AnnotationTypes, DocumentTypes
|
|
19
|
-
|
|
20
20
|
from .annotation import Annotation, Document
|
|
21
21
|
from .model import MmifObject, DataList
|
|
22
22
|
from .view import View
|
|
@@ -32,9 +32,6 @@ class Mmif(MmifObject):
|
|
|
32
32
|
:param validate: whether to validate the data against the MMIF JSON schema.
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
view_prefix: ClassVar[str] = 'v_'
|
|
36
|
-
id_delimiter: ClassVar[str] = ':'
|
|
37
|
-
|
|
38
35
|
def __init__(self, mmif_obj: Optional[Union[bytes, str, dict]] = None, *, validate: bool = True) -> None:
|
|
39
36
|
self.metadata: MmifMetadata = MmifMetadata()
|
|
40
37
|
self.documents: DocumentsList = DocumentsList()
|
|
@@ -362,8 +359,8 @@ class Mmif(MmifObject):
|
|
|
362
359
|
:return: a reference to the corresponding document, if it exists
|
|
363
360
|
:raises KeyError: if there is no corresponding document
|
|
364
361
|
"""
|
|
365
|
-
if
|
|
366
|
-
vid, did = doc_id.split(
|
|
362
|
+
if self.id_delimiter in doc_id:
|
|
363
|
+
vid, did = doc_id.split(self.id_delimiter)
|
|
367
364
|
view = self[vid]
|
|
368
365
|
if isinstance(view, View):
|
|
369
366
|
return view.get_document_by_id(did)
|
|
@@ -410,8 +407,8 @@ class Mmif(MmifObject):
|
|
|
410
407
|
aligned_types = set()
|
|
411
408
|
for ann_id in [alignment['target'], alignment['source']]:
|
|
412
409
|
ann_id = cast(str, ann_id)
|
|
413
|
-
if
|
|
414
|
-
view_id, ann_id = ann_id.split(
|
|
410
|
+
if self.id_delimiter in ann_id:
|
|
411
|
+
view_id, ann_id = ann_id.split(self.id_delimiter)
|
|
415
412
|
aligned_type = cast(Annotation, self[view_id][ann_id]).at_type
|
|
416
413
|
else:
|
|
417
414
|
aligned_type = cast(Annotation, alignment_view[ann_id]).at_type
|
|
@@ -438,8 +435,8 @@ class Mmif(MmifObject):
|
|
|
438
435
|
except StopIteration:
|
|
439
436
|
# means search failed by the full doc_id string,
|
|
440
437
|
# now try trimming the view_id from the string and re-do the search
|
|
441
|
-
if
|
|
442
|
-
vid, did = doc_id.split(
|
|
438
|
+
if self.id_delimiter in doc_id:
|
|
439
|
+
vid, did = doc_id.split(self.id_delimiter)
|
|
443
440
|
if view.id == vid:
|
|
444
441
|
annotations = view.get_annotations(document=did)
|
|
445
442
|
try:
|
|
@@ -450,6 +447,16 @@ class Mmif(MmifObject):
|
|
|
450
447
|
pass
|
|
451
448
|
return views
|
|
452
449
|
|
|
450
|
+
def get_all_views_with_error(self) -> List[View]:
|
|
451
|
+
"""
|
|
452
|
+
Returns the list of all views in the MMIF that have errors.
|
|
453
|
+
|
|
454
|
+
:return: the list of views that contain errors but no annotations
|
|
455
|
+
"""
|
|
456
|
+
return [v for v in self.views if v.has_error()]
|
|
457
|
+
|
|
458
|
+
get_views_with_error = get_all_views_with_error
|
|
459
|
+
|
|
453
460
|
def get_all_views_contain(self, at_types: Union[ThingTypesBase, str, List[Union[str, ThingTypesBase]]]) -> List[View]:
|
|
454
461
|
"""
|
|
455
462
|
Returns the list of all views in the MMIF if given types
|
|
@@ -464,11 +471,27 @@ class Mmif(MmifObject):
|
|
|
464
471
|
else:
|
|
465
472
|
return [view for view in self.views if at_types in view.metadata.contains]
|
|
466
473
|
|
|
467
|
-
|
|
474
|
+
get_views_contain = get_all_views_contain
|
|
475
|
+
|
|
476
|
+
def get_view_with_error(self) -> Optional[View]:
|
|
468
477
|
"""
|
|
469
|
-
|
|
478
|
+
Returns the last view appended that contains an error.
|
|
479
|
+
|
|
480
|
+
:return: the view, or None if no error is found
|
|
481
|
+
"""
|
|
482
|
+
for view in reversed(self.views):
|
|
483
|
+
if view.has_error():
|
|
484
|
+
return view
|
|
485
|
+
return None
|
|
486
|
+
|
|
487
|
+
def get_last_error(self) -> Optional[str]:
|
|
488
|
+
"""
|
|
489
|
+
Returns the last error message found in the views.
|
|
490
|
+
|
|
491
|
+
:return: the error message in human-readable format, or None if no error is found
|
|
470
492
|
"""
|
|
471
|
-
|
|
493
|
+
v = self.get_view_with_error()
|
|
494
|
+
return v.get_error() if v is not None else None
|
|
472
495
|
|
|
473
496
|
def get_view_contains(self, at_types: Union[ThingTypesBase, str, List[Union[str, ThingTypesBase]]]) -> Optional[View]:
|
|
474
497
|
"""
|
|
@@ -506,8 +529,8 @@ class Mmif(MmifObject):
|
|
|
506
529
|
elif 'targets' in props:
|
|
507
530
|
|
|
508
531
|
def get_target_ann(cur_ann, target_id):
|
|
509
|
-
if
|
|
510
|
-
target_id =
|
|
532
|
+
if self.id_delimiter not in target_id:
|
|
533
|
+
target_id = self.id_delimiter.join((cur_ann.parent, target_id))
|
|
511
534
|
return self.__getitem__(target_id)
|
|
512
535
|
|
|
513
536
|
if not targets_sorted:
|
|
@@ -549,7 +572,7 @@ class Mmif(MmifObject):
|
|
|
549
572
|
"""
|
|
550
573
|
if item in self._named_attributes():
|
|
551
574
|
return self.__dict__[item]
|
|
552
|
-
split_attempt = item.split(
|
|
575
|
+
split_attempt = item.split(self.id_delimiter)
|
|
553
576
|
|
|
554
577
|
document_result = self.documents.get(split_attempt[0])
|
|
555
578
|
view_result = self.views.get(split_attempt[0])
|
|
@@ -14,7 +14,7 @@ for the different components of MMIF is added in the subclasses.
|
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
16
|
from datetime import datetime
|
|
17
|
-
from typing import Union, Any, Dict, Optional, TypeVar, Generic, Generator, Iterator, Type, Set
|
|
17
|
+
from typing import Union, Any, Dict, Optional, TypeVar, Generic, Generator, Iterator, Type, Set, ClassVar
|
|
18
18
|
|
|
19
19
|
from deepdiff import DeepDiff
|
|
20
20
|
|
|
@@ -44,41 +44,48 @@ class MmifObject(object):
|
|
|
44
44
|
|
|
45
45
|
1. _unnamed_attributes:
|
|
46
46
|
Only can be either None or an empty dictionary. If it's set to None,
|
|
47
|
-
it means the class won't take any ``Additional Attributes`` in the
|
|
48
|
-
schema sense. If it's
|
|
49
|
-
class,
|
|
47
|
+
it means the class won't take any ``Additional Attributes`` in the
|
|
48
|
+
JSON schema sense. If it's an empty dict, users can throw any k-v
|
|
49
|
+
pairs to the class, as long as the key is not a "reserved" name,
|
|
50
|
+
and those additional attributes will be stored in this dict while
|
|
51
|
+
in memory.
|
|
50
52
|
2. _attribute_classes:
|
|
51
53
|
This is a dict from a key name to a specific python class to use for
|
|
52
54
|
deserialize the value. Note that a key name in this dict does NOT
|
|
53
55
|
have to be a *named* attribute, but is recommended to be one.
|
|
54
56
|
3. _required_attributes:
|
|
55
|
-
This is a simple list of names of attributes that are required in
|
|
56
|
-
When serialize, an object will skip its *empty* (e.g.
|
|
57
|
-
attributes unless they are in this list.
|
|
58
|
-
string would have empty
|
|
57
|
+
This is a simple list of names of attributes that are required in
|
|
58
|
+
the object. When serialize, an object will skip its *empty* (e.g.
|
|
59
|
+
zero-length, or None) attributes unless they are in this list.
|
|
60
|
+
Otherwise, the serialized JSON string would have empty
|
|
61
|
+
representations (e.g. ``""``, ``[]``).
|
|
59
62
|
4. _exclude_from_diff:
|
|
60
|
-
This is a simple list of names of attributes that should be excluded
|
|
61
|
-
the diff calculation in ``__eq__``.
|
|
63
|
+
This is a simple list of names of attributes that should be excluded
|
|
64
|
+
from the diff calculation in ``__eq__``.
|
|
62
65
|
|
|
63
66
|
# TODO (krim @ 8/17/20): this dict is however, a duplicate with the type hints in the class definition.
|
|
64
|
-
Maybe there is a better way to utilize type hints (e.g. getting them
|
|
65
|
-
|
|
67
|
+
Maybe there is a better way to utilize type hints (e.g. getting them
|
|
68
|
+
as a programmatically), but for now developers should be careful to
|
|
69
|
+
add types to hints as well as to this dict.
|
|
66
70
|
|
|
67
71
|
Also note that those special attributes MUST be set in the __init__()
|
|
68
72
|
before calling super method, otherwise deserialization will not work.
|
|
69
73
|
|
|
70
74
|
And also, a subclass that has one or more *named* attributes, it must
|
|
71
|
-
set those attributes in the __init__() before calling super method.
|
|
72
|
-
serializing a MmifObject, all *empty* attributes will be ignored,
|
|
73
|
-
optional named attributes, you must leave the values empty
|
|
74
|
-
NOT None. Any None-valued named attributes will cause
|
|
75
|
-
implementation.
|
|
75
|
+
set those attributes in the __init__() before calling super method.
|
|
76
|
+
When serializing a MmifObject, all *empty* attributes will be ignored,
|
|
77
|
+
so for optional named attributes, you must leave the values empty
|
|
78
|
+
(len == 0), but NOT None. Any None-valued named attributes will cause
|
|
79
|
+
issues with current implementation.
|
|
76
80
|
|
|
77
81
|
:param mmif_obj: JSON string or `dict` to initialize an object.
|
|
78
82
|
If not given, an empty object will be initialized, sometimes with
|
|
79
83
|
an ID value automatically generated, based on its parent object.
|
|
80
84
|
"""
|
|
81
85
|
|
|
86
|
+
view_prefix: ClassVar[str] = 'v_'
|
|
87
|
+
id_delimiter: ClassVar[str] = ':'
|
|
88
|
+
|
|
82
89
|
# these are the reserved names that cannot be used as attribute names, and
|
|
83
90
|
# they won't be serialized
|
|
84
91
|
reserved_names: Set[str] = {
|
|
@@ -269,8 +276,8 @@ class MmifObject(object):
|
|
|
269
276
|
"""
|
|
270
277
|
Returns number of attributes that are not *empty*.
|
|
271
278
|
"""
|
|
272
|
-
return sum([named in self and not self.is_empty(self[named]) for named in self._named_attributes()])
|
|
273
|
-
|
|
279
|
+
return (sum([named in self and not self.is_empty(self[named]) for named in self._named_attributes()])
|
|
280
|
+
+ (len(self._unnamed_attributes) if self._unnamed_attributes else 0))
|
|
274
281
|
|
|
275
282
|
def __setitem__(self, key, value) -> None:
|
|
276
283
|
if key in self.reserved_names:
|
|
@@ -5,6 +5,7 @@ as a live Python object.
|
|
|
5
5
|
In MMIF, views are created by apps in a pipeline that are annotating
|
|
6
6
|
data that was previously present in the MMIF file.
|
|
7
7
|
"""
|
|
8
|
+
import json
|
|
8
9
|
from datetime import datetime
|
|
9
10
|
from typing import Dict, Union, Optional, Generator, List, cast
|
|
10
11
|
|
|
@@ -230,6 +231,23 @@ class View(MmifObject):
|
|
|
230
231
|
def set_error(self, err_message: str, err_trace: str) -> None:
|
|
231
232
|
self.metadata.set_error(err_message, err_trace)
|
|
232
233
|
self.annotations.empty()
|
|
234
|
+
|
|
235
|
+
def get_error(self) -> Optional[str]:
|
|
236
|
+
"""
|
|
237
|
+
Get the "text" representation of the error occurred during
|
|
238
|
+
processing. Text representation is supposed to be human-readable.
|
|
239
|
+
When ths view does not have any error, returns None.
|
|
240
|
+
"""
|
|
241
|
+
if self.has_error():
|
|
242
|
+
return self.metadata.get_error_as_text()
|
|
243
|
+
else:
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
def has_error(self) -> bool:
|
|
247
|
+
return self.metadata.has_error()
|
|
248
|
+
|
|
249
|
+
def has_warnings(self):
|
|
250
|
+
return self.metadata.has_warnings()
|
|
233
251
|
|
|
234
252
|
|
|
235
253
|
class ViewMetadata(MmifObject):
|
|
@@ -267,7 +285,24 @@ class ViewMetadata(MmifObject):
|
|
|
267
285
|
if not (self.contains.items() or self.error or self.warnings):
|
|
268
286
|
serialized['contains'] = {}
|
|
269
287
|
return serialized
|
|
270
|
-
|
|
288
|
+
|
|
289
|
+
def has_error(self) -> bool:
|
|
290
|
+
return len(self.error) > 0
|
|
291
|
+
|
|
292
|
+
def has_warnings(self):
|
|
293
|
+
return len(self.warnings) > 0
|
|
294
|
+
|
|
295
|
+
def get_error_as_text(self) -> str:
|
|
296
|
+
if self.has_error():
|
|
297
|
+
if isinstance(self.error, ErrorDict):
|
|
298
|
+
return str(self.error)
|
|
299
|
+
elif isinstance(self.error, dict):
|
|
300
|
+
return f"Error: {json.dumps(self.error, indent=2)}"
|
|
301
|
+
else:
|
|
302
|
+
return f"Error (unknown error format): {self.error}"
|
|
303
|
+
else:
|
|
304
|
+
raise KeyError(f"No error found")
|
|
305
|
+
|
|
271
306
|
def new_contain(self, at_type: Union[str, ThingTypesBase], **contains_metadata) -> Optional['Contain']:
|
|
272
307
|
"""
|
|
273
308
|
Adds a new element to the ``contains`` dictionary.
|
|
@@ -346,6 +381,9 @@ class ErrorDict(MmifObject):
|
|
|
346
381
|
self.message: str = ''
|
|
347
382
|
self.stackTrace: str = ''
|
|
348
383
|
super().__init__(error_obj)
|
|
384
|
+
|
|
385
|
+
def __str__(self):
|
|
386
|
+
return f"({self.message})\n\n{self.stackTrace}"
|
|
349
387
|
|
|
350
388
|
|
|
351
389
|
class Contain(DataDict[str, str]):
|
|
@@ -68,17 +68,17 @@ def convert(t: Union[int, float, str], in_unit: str, out_unit: str, fps: float)
|
|
|
68
68
|
elif out_unit == 'frame':
|
|
69
69
|
# ms>f
|
|
70
70
|
if 'millisecond' == in_unit:
|
|
71
|
-
return
|
|
71
|
+
return round(t / 1000 * fps)
|
|
72
72
|
# s>f
|
|
73
73
|
elif 'second' == in_unit:
|
|
74
|
-
return
|
|
74
|
+
return round(t * fps)
|
|
75
75
|
# s>(ms or i)
|
|
76
76
|
elif in_unit == 'second':
|
|
77
|
-
return
|
|
77
|
+
return round(t * 1000) if out_unit == 'millisecond' else _second_to_isoformat(t)
|
|
78
78
|
# ms>(s or i)
|
|
79
79
|
elif in_unit == 'millisecond':
|
|
80
80
|
return t / 1000 if out_unit == 'second' else _millisecond_to_isoformat(t)
|
|
81
81
|
# f>ms, f>s
|
|
82
82
|
else:
|
|
83
|
-
return (t / fps) if out_unit == 'second' else (round(t / fps, 3) * 1000) # pytype: disable=bad-return-type
|
|
83
|
+
return (t / fps) if out_unit == 'second' else round(round(t / fps, 3) * 1000) # pytype: disable=bad-return-type
|
|
84
84
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import warnings
|
|
3
3
|
from typing import List, Union, Tuple
|
|
4
|
+
import math
|
|
4
5
|
|
|
5
6
|
import mmif
|
|
6
7
|
from mmif import Annotation, Document, Mmif
|
|
@@ -124,22 +125,23 @@ def extract_mid_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
|
|
|
124
125
|
return extract_frames_as_images(vd, [get_mid_framenum(mmif, time_frame)], as_PIL=as_PIL)[0]
|
|
125
126
|
|
|
126
127
|
|
|
127
|
-
def sample_frames(start_frame: int, end_frame: int,
|
|
128
|
+
def sample_frames(start_frame: int, end_frame: int, sample_rate: float = 1) -> List[int]:
|
|
128
129
|
"""
|
|
129
130
|
Helper function to sample frames from a time interval.
|
|
130
|
-
Can also be used as a "cutoff" function when used with ``start_frame==0`` and ``
|
|
131
|
+
Can also be used as a "cutoff" function when used with ``start_frame==0`` and ``sample_rate==1``.
|
|
131
132
|
|
|
132
133
|
:param start_frame: start frame of the interval
|
|
133
134
|
:param end_frame: end frame of the interval
|
|
134
|
-
:param
|
|
135
|
+
:param sample_rate: sampling rate (or step) to configure how often to take a frame, default is 1, meaning all consecutive frames are sampled
|
|
135
136
|
|
|
136
137
|
"""
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
raise ValueError(f"Sample ratio must be greater than 1, but got {sample_ratio}")
|
|
138
|
+
if sample_rate < 1:
|
|
139
|
+
raise ValueError(f"Sample rate must be greater than 1, but got {sample_rate}")
|
|
140
140
|
frame_nums: List[int] = []
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
cur_f = start_frame
|
|
142
|
+
while cur_f < end_frame:
|
|
143
|
+
frame_nums.append(math.ceil(cur_f))
|
|
144
|
+
cur_f += sample_rate
|
|
143
145
|
return frame_nums
|
|
144
146
|
|
|
145
147
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mmif-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.14
|
|
4
4
|
Summary: Python implementation of MultiMedia Interchange Format specification. (https://mmif.clams.ai)
|
|
5
5
|
Home-page: https://mmif.clams.ai
|
|
6
6
|
Author: Brandeis Lab for Linguistics and Computation
|
mmif-python-1.0.12/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.0.12
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|