mmif-python 1.0.14__tar.gz → 1.0.15__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.15/PKG-INFO +46 -0
- mmif_python-1.0.15/VERSION +1 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/serialize/mmif.py +120 -112
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/utils/video_document_helper.py +40 -2
- mmif_python-1.0.15/mmif/ver/__init__.py +2 -0
- mmif_python-1.0.15/mmif_python.egg-info/PKG-INFO +46 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif_python.egg-info/SOURCES.txt +5 -1
- mmif_python-1.0.15/tests/test_serialize.py +1354 -0
- mmif_python-1.0.15/tests/test_utils.py +176 -0
- mmif_python-1.0.15/tests/test_versioncompat.py +56 -0
- mmif_python-1.0.15/tests/test_vocab.py +56 -0
- mmif-python-1.0.14/PKG-INFO +0 -37
- mmif-python-1.0.14/VERSION +0 -1
- mmif-python-1.0.14/mmif/ver/__init__.py +0 -2
- mmif-python-1.0.14/mmif_python.egg-info/PKG-INFO +0 -37
- {mmif-python-1.0.14 → mmif_python-1.0.15}/LICENSE +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/MANIFEST.in +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/README.md +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/__init__.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/res/__init__.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/res/clams.vocabulary.yaml +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/res/do-not-edit.txt +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/res/mmif.json +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/serialize/__init__.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/serialize/annotation.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/serialize/model.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/serialize/view.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/utils/__init__.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/utils/sequence_helper.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/utils/timeunit_helper.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/ver/do-not-edit.txt +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/vocabulary/__init__.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/vocabulary/annotation_types.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/vocabulary/base_types.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/vocabulary/do-not-edit.txt +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/vocabulary/document_types.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif_docloc_http/__init__.py +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif_python.egg-info/dependency_links.txt +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif_python.egg-info/requires.txt +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif_python.egg-info/top_level.txt +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/requirements.cv +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/requirements.seq +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/requirements.txt +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/setup.cfg +0 -0
- {mmif-python-1.0.14 → mmif_python-1.0.15}/setup.py +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mmif-python
|
|
3
|
+
Version: 1.0.15
|
|
4
|
+
Summary: Python implementation of MultiMedia Interchange Format specification. (https://mmif.clams.ai)
|
|
5
|
+
Home-page: https://mmif.clams.ai
|
|
6
|
+
Author: Brandeis Lab for Linguistics and Computation
|
|
7
|
+
Author-email: admin@clams.ai
|
|
8
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: deepdiff>5
|
|
16
|
+
Requires-Dist: jsonschema
|
|
17
|
+
Provides-Extra: seq
|
|
18
|
+
Requires-Dist: numpy; extra == "seq"
|
|
19
|
+
Provides-Extra: cv
|
|
20
|
+
Requires-Dist: pillow; extra == "cv"
|
|
21
|
+
Requires-Dist: opencv-python; extra == "cv"
|
|
22
|
+
Requires-Dist: ffmpeg-python; extra == "cv"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-pep8; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
27
|
+
Requires-Dist: pytype; extra == "dev"
|
|
28
|
+
|
|
29
|
+
## MultiMedia Interchange Format
|
|
30
|
+
[MMIF](https://mmif.clams.ai) is a JSON(-LD)-based data format designed for transferring annotation data between computational analysis applications in [CLAMS project](https://clams.ai).
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## mmif-python
|
|
34
|
+
`mmif-python` is a Python implementation of the MMIF data format.
|
|
35
|
+
`mmif-python` provides various helper classes and functions to handle MMIF JSON in Python,
|
|
36
|
+
including ;
|
|
37
|
+
|
|
38
|
+
1. de-/serialization of MMIF internal data structures to/from JSON
|
|
39
|
+
2. validation of MMIF JSON
|
|
40
|
+
3. handling of CLAMS vocabulary types
|
|
41
|
+
4. navigation of MMIF object via various "search" methods (e.g. `mmif.get_all_views_contain(vocab_type))`)
|
|
42
|
+
|
|
43
|
+
## For more ...
|
|
44
|
+
* [Version history and patch notes](https://github.com/clamsproject/mmif-python/blob/main/CHANGELOG.md)
|
|
45
|
+
* [MMIF Python API documentation](https://clamsproject.github.io/mmif-python)
|
|
46
|
+
* [MMIF JSON specification and schema](https://clamsproject.github.io/mmif)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.15
|
|
@@ -24,6 +24,104 @@ from .view import View
|
|
|
24
24
|
__all__ = ['Mmif']
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class MmifMetadata(MmifObject):
|
|
28
|
+
"""
|
|
29
|
+
Basic MmifObject class to contain the top-level metadata of a MMIF file.
|
|
30
|
+
|
|
31
|
+
:param metadata_obj: the JSON data
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, metadata_obj: Optional[Union[bytes, str, dict]] = None) -> None:
|
|
35
|
+
# TODO (krim @ 10/7/20): there could be a better name and a better way to give a value to this
|
|
36
|
+
self.mmif: str = f"http://mmif.clams.ai/{mmif.__specver__}"
|
|
37
|
+
self._required_attributes = ["mmif"]
|
|
38
|
+
super().__init__(metadata_obj)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DocumentsList(DataList[Document]):
|
|
42
|
+
"""
|
|
43
|
+
DocumentsList object that implements :class:`mmif.serialize.model.DataList`
|
|
44
|
+
for :class:`mmif.serialize.document.Document`.
|
|
45
|
+
"""
|
|
46
|
+
_items: Dict[str, Document]
|
|
47
|
+
|
|
48
|
+
def _deserialize(self, input_list: list) -> None: # pytype: disable=signature-mismatch
|
|
49
|
+
"""
|
|
50
|
+
Extends base ``_deserialize`` method to initialize ``items`` as a dict from
|
|
51
|
+
document IDs to :class:`mmif.serialize.document.Document` objects.
|
|
52
|
+
|
|
53
|
+
:param input_list: the JSON data that defines the list of documents
|
|
54
|
+
:return: None
|
|
55
|
+
"""
|
|
56
|
+
self._items = {item['properties']['id']: Document(item) for item in input_list}
|
|
57
|
+
|
|
58
|
+
def append(self, value: Document, overwrite=False) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Appends a document to the list.
|
|
61
|
+
|
|
62
|
+
Fails if there is already a document with the same ID
|
|
63
|
+
in the list, unless ``overwrite`` is set to True.
|
|
64
|
+
|
|
65
|
+
:param value: the :class:`mmif.serialize.document.Document`
|
|
66
|
+
object to add
|
|
67
|
+
:param overwrite: if set to True, will overwrite an
|
|
68
|
+
existing document with the same ID
|
|
69
|
+
:raises KeyError: if ``overwrite`` is set to False and
|
|
70
|
+
a document with the same ID exists
|
|
71
|
+
in the list
|
|
72
|
+
:return: None
|
|
73
|
+
"""
|
|
74
|
+
super()._append_with_key(value.id, value, overwrite)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ViewsList(DataList[View]):
|
|
78
|
+
"""
|
|
79
|
+
ViewsList object that implements :class:`mmif.serialize.model.DataList`
|
|
80
|
+
for :class:`mmif.serialize.view.View`.
|
|
81
|
+
"""
|
|
82
|
+
_items: Dict[str, View]
|
|
83
|
+
|
|
84
|
+
def __init__(self, mmif_obj: Optional[Union[bytes, str, list]] = None):
|
|
85
|
+
super().__init__(mmif_obj)
|
|
86
|
+
|
|
87
|
+
def _deserialize(self, input_list: list) -> None: # pytype: disable=signature-mismatch
|
|
88
|
+
"""
|
|
89
|
+
Extends base ``_deserialize`` method to initialize ``items`` as a dict from
|
|
90
|
+
view IDs to :class:`mmif.serialize.view.View` objects.
|
|
91
|
+
|
|
92
|
+
:param input_list: the JSON data that defines the list of views
|
|
93
|
+
:return: None
|
|
94
|
+
"""
|
|
95
|
+
if input_list:
|
|
96
|
+
self._items = {item['id']: View(item) for item in input_list}
|
|
97
|
+
|
|
98
|
+
def append(self, value: View, overwrite=False) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Appends a view to the list.
|
|
101
|
+
|
|
102
|
+
Fails if there is already a view with the same ID
|
|
103
|
+
in the list, unless ``overwrite`` is set to True.
|
|
104
|
+
|
|
105
|
+
:param value: the :class:`mmif.serialize.view.View`
|
|
106
|
+
object to add
|
|
107
|
+
:param overwrite: if set to True, will overwrite an
|
|
108
|
+
existing view with the same ID
|
|
109
|
+
:raises KeyError: if ``overwrite`` is set to False and
|
|
110
|
+
a view with the same ID exists
|
|
111
|
+
in the list
|
|
112
|
+
:return: None
|
|
113
|
+
"""
|
|
114
|
+
super()._append_with_key(value.id, value, overwrite)
|
|
115
|
+
|
|
116
|
+
def get_last(self) -> Optional[View]:
|
|
117
|
+
"""
|
|
118
|
+
Returns the last view appended to the list.
|
|
119
|
+
"""
|
|
120
|
+
for view in reversed(self._items.values()):
|
|
121
|
+
if 'error' not in view.metadata and 'warning' not in view.metadata:
|
|
122
|
+
return view
|
|
123
|
+
|
|
124
|
+
|
|
27
125
|
class Mmif(MmifObject):
|
|
28
126
|
"""
|
|
29
127
|
MmifObject that represents a full MMIF file.
|
|
@@ -560,131 +658,41 @@ class Mmif(MmifObject):
|
|
|
560
658
|
"""
|
|
561
659
|
return self._get_linear_anchor_point(annotation, start=False)
|
|
562
660
|
|
|
563
|
-
|
|
564
|
-
|
|
661
|
+
def __getitem__(self, item: str) \
|
|
662
|
+
-> Union[Document, View, Annotation, MmifMetadata, DocumentsList, ViewsList]:
|
|
565
663
|
"""
|
|
566
|
-
getitem implementation for Mmif.
|
|
567
|
-
rather than returning a None
|
|
664
|
+
getitem implementation for Mmif. This will try to find any object, given an identifier or an immediate
|
|
665
|
+
attribute name. When nothing is found, this will raise an error rather than returning a None
|
|
568
666
|
|
|
569
667
|
:raises KeyError: if the item is not found or if the search results are ambiguous
|
|
570
|
-
:param item:
|
|
668
|
+
:param item: an attribute name or an object identifier (a document ID, a view ID, or an annotation ID). When
|
|
669
|
+
annotation ID is given as a "short" ID (without view ID prefix), the method will try to find a
|
|
670
|
+
match from the first view, and return immediately if found.
|
|
571
671
|
:return: the object searched for
|
|
672
|
+
:raise KeyError: if the item is not found or multiple objects are found with the same ID
|
|
572
673
|
"""
|
|
573
674
|
if item in self._named_attributes():
|
|
574
675
|
return self.__dict__[item]
|
|
575
676
|
split_attempt = item.split(self.id_delimiter)
|
|
576
677
|
|
|
577
|
-
|
|
578
|
-
view_result = self.views.get(split_attempt[0])
|
|
678
|
+
found = []
|
|
579
679
|
|
|
580
680
|
if len(split_attempt) == 1:
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
681
|
+
found.append(self.documents.get(split_attempt[0]))
|
|
682
|
+
found.append(self.views.get(split_attempt[0]))
|
|
683
|
+
for view in self.views:
|
|
684
|
+
found.append(view.annotations.get(split_attempt[0]))
|
|
685
|
+
elif len(split_attempt) == 2:
|
|
686
|
+
v = self.get_view_by_id(split_attempt[0])
|
|
687
|
+
if v is not None:
|
|
688
|
+
found.append(v.annotations.get(split_attempt[1]))
|
|
584
689
|
else:
|
|
585
690
|
raise KeyError("Tried to subscript into a view that doesn't exist")
|
|
691
|
+
found = [x for x in found if x is not None]
|
|
586
692
|
|
|
587
|
-
if
|
|
693
|
+
if len(found) > 1:
|
|
588
694
|
raise KeyError("Ambiguous ID search result")
|
|
589
|
-
|
|
695
|
+
elif len(found) == 0:
|
|
590
696
|
raise KeyError("ID not found: %s" % item)
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
class MmifMetadata(MmifObject):
|
|
596
|
-
"""
|
|
597
|
-
Basic MmifObject class to contain the top-level metadata of a MMIF file.
|
|
598
|
-
|
|
599
|
-
:param metadata_obj: the JSON data
|
|
600
|
-
"""
|
|
601
|
-
|
|
602
|
-
def __init__(self, metadata_obj: Optional[Union[bytes, str, dict]] = None) -> None:
|
|
603
|
-
# TODO (krim @ 10/7/20): there could be a better name and a better way to give a value to this
|
|
604
|
-
self.mmif: str = f"http://mmif.clams.ai/{mmif.__specver__}"
|
|
605
|
-
self._required_attributes = ["mmif"]
|
|
606
|
-
super().__init__(metadata_obj)
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
class DocumentsList(DataList[Document]):
|
|
610
|
-
"""
|
|
611
|
-
DocumentsList object that implements :class:`mmif.serialize.model.DataList`
|
|
612
|
-
for :class:`mmif.serialize.document.Document`.
|
|
613
|
-
"""
|
|
614
|
-
_items: Dict[str, Document]
|
|
615
|
-
|
|
616
|
-
def _deserialize(self, input_list: list) -> None: # pytype: disable=signature-mismatch
|
|
617
|
-
"""
|
|
618
|
-
Extends base ``_deserialize`` method to initialize ``items`` as a dict from
|
|
619
|
-
document IDs to :class:`mmif.serialize.document.Document` objects.
|
|
620
|
-
|
|
621
|
-
:param input_list: the JSON data that defines the list of documents
|
|
622
|
-
:return: None
|
|
623
|
-
"""
|
|
624
|
-
self._items = {item['properties']['id']: Document(item) for item in input_list}
|
|
625
|
-
|
|
626
|
-
def append(self, value: Document, overwrite=False) -> None:
|
|
627
|
-
"""
|
|
628
|
-
Appends a document to the list.
|
|
629
|
-
|
|
630
|
-
Fails if there is already a document with the same ID
|
|
631
|
-
in the list, unless ``overwrite`` is set to True.
|
|
632
|
-
|
|
633
|
-
:param value: the :class:`mmif.serialize.document.Document`
|
|
634
|
-
object to add
|
|
635
|
-
:param overwrite: if set to True, will overwrite an
|
|
636
|
-
existing document with the same ID
|
|
637
|
-
:raises KeyError: if ``overwrite`` is set to False and
|
|
638
|
-
a document with the same ID exists
|
|
639
|
-
in the list
|
|
640
|
-
:return: None
|
|
641
|
-
"""
|
|
642
|
-
super()._append_with_key(value.id, value, overwrite)
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
class ViewsList(DataList[View]):
|
|
646
|
-
"""
|
|
647
|
-
ViewsList object that implements :class:`mmif.serialize.model.DataList`
|
|
648
|
-
for :class:`mmif.serialize.view.View`.
|
|
649
|
-
"""
|
|
650
|
-
_items: Dict[str, View]
|
|
651
|
-
|
|
652
|
-
def __init__(self, mmif_obj: Optional[Union[bytes, str, list]] = None):
|
|
653
|
-
super().__init__(mmif_obj)
|
|
654
|
-
|
|
655
|
-
def _deserialize(self, input_list: list) -> None: # pytype: disable=signature-mismatch
|
|
656
|
-
"""
|
|
657
|
-
Extends base ``_deserialize`` method to initialize ``items`` as a dict from
|
|
658
|
-
view IDs to :class:`mmif.serialize.view.View` objects.
|
|
659
|
-
|
|
660
|
-
:param input_list: the JSON data that defines the list of views
|
|
661
|
-
:return: None
|
|
662
|
-
"""
|
|
663
|
-
if input_list:
|
|
664
|
-
self._items = {item['id']: View(item) for item in input_list}
|
|
665
|
-
|
|
666
|
-
def append(self, value: View, overwrite=False) -> None:
|
|
667
|
-
"""
|
|
668
|
-
Appends a view to the list.
|
|
669
|
-
|
|
670
|
-
Fails if there is already a view with the same ID
|
|
671
|
-
in the list, unless ``overwrite`` is set to True.
|
|
672
|
-
|
|
673
|
-
:param value: the :class:`mmif.serialize.view.View`
|
|
674
|
-
object to add
|
|
675
|
-
:param overwrite: if set to True, will overwrite an
|
|
676
|
-
existing view with the same ID
|
|
677
|
-
:raises KeyError: if ``overwrite`` is set to False and
|
|
678
|
-
a view with the same ID exists
|
|
679
|
-
in the list
|
|
680
|
-
:return: None
|
|
681
|
-
"""
|
|
682
|
-
super()._append_with_key(value.id, value, overwrite)
|
|
683
|
-
|
|
684
|
-
def get_last(self) -> Optional[View]:
|
|
685
|
-
"""
|
|
686
|
-
Returns the last view appended to the list.
|
|
687
|
-
"""
|
|
688
|
-
for view in reversed(self._items.values()):
|
|
689
|
-
if 'error' not in view.metadata and 'warning' not in view.metadata:
|
|
690
|
-
return view
|
|
697
|
+
else:
|
|
698
|
+
return found[-1]
|
|
@@ -6,7 +6,7 @@ import math
|
|
|
6
6
|
import mmif
|
|
7
7
|
from mmif import Annotation, Document, Mmif
|
|
8
8
|
from mmif.utils.timeunit_helper import convert
|
|
9
|
-
from mmif.vocabulary import DocumentTypes
|
|
9
|
+
from mmif.vocabulary import DocumentTypes, AnnotationTypes
|
|
10
10
|
|
|
11
11
|
for cv_dep in ('cv2', 'ffmpeg', 'PIL'):
|
|
12
12
|
try:
|
|
@@ -83,14 +83,16 @@ def extract_frames_as_images(video_document: Document, framenums: List[int], as_
|
|
|
83
83
|
frames = []
|
|
84
84
|
video = capture(video_document)
|
|
85
85
|
cur_f = 0
|
|
86
|
+
tot_fcount = video_document.get_property(FRAMECOUNT_DOCPROP_KEY)
|
|
86
87
|
while True:
|
|
87
|
-
if not framenums or cur_f >
|
|
88
|
+
if not framenums or cur_f > tot_fcount:
|
|
88
89
|
break
|
|
89
90
|
ret, frame = video.read()
|
|
90
91
|
if cur_f == framenums[0]:
|
|
91
92
|
if not ret:
|
|
92
93
|
sec = convert(cur_f, 'f', 's', video_document.get_property(FPS_DOCPROP_KEY))
|
|
93
94
|
warnings.warn(f'Frame #{cur_f} ({sec}s) could not be read from the video {video_document.id}.')
|
|
95
|
+
cur_f += 1
|
|
94
96
|
continue
|
|
95
97
|
frames.append(Image.fromarray(frame[:, :, ::-1]) if as_PIL else frame)
|
|
96
98
|
framenums.pop(0)
|
|
@@ -125,6 +127,42 @@ def extract_mid_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
|
|
|
125
127
|
return extract_frames_as_images(vd, [get_mid_framenum(mmif, time_frame)], as_PIL=as_PIL)[0]
|
|
126
128
|
|
|
127
129
|
|
|
130
|
+
def get_representative_framenum(mmif: Mmif, time_frame: Annotation):
|
|
131
|
+
"""
|
|
132
|
+
Calculates the representative frame number from an annotation.
|
|
133
|
+
|
|
134
|
+
:param mmif: :py:class:`~mmif.serialize.mmif.Mmif` instance
|
|
135
|
+
:param time_frame: :py:class:`~mmif.serialize.annotation.Annotation` instance that holds a time interval annotation containing a `representatives` property (``"@type": ".../TimeFrame/..."``)
|
|
136
|
+
:return: representative frame number as an integer
|
|
137
|
+
"""
|
|
138
|
+
if 'representatives' not in time_frame.properties:
|
|
139
|
+
raise ValueError(f'The time frame {time_frame.id} does not have a representative.')
|
|
140
|
+
timeunit = time_frame.get_property('timeUnit')
|
|
141
|
+
video_document = mmif[time_frame.get_property('document')]
|
|
142
|
+
fps = get_framerate(video_document)
|
|
143
|
+
representatives = time_frame.get_property('representatives')
|
|
144
|
+
top_representative_id = representatives[0]
|
|
145
|
+
try:
|
|
146
|
+
representative_timepoint_anno = mmif[time_frame._parent_view_id+time_frame.id_delimiter+top_representative_id]
|
|
147
|
+
except KeyError:
|
|
148
|
+
raise ValueError(f'Representative timepoint {top_representative_id} not found in any view.')
|
|
149
|
+
return convert(representative_timepoint_anno.get_property('timePoint'), timeunit, 'frame', fps)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def extract_representative_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
|
|
153
|
+
"""
|
|
154
|
+
Extracts the representative frame of an annotation as a numpy ndarray or PIL Image.
|
|
155
|
+
|
|
156
|
+
:param mmif: :py:class:`~mmif.serialize.mmif.Mmif` instance
|
|
157
|
+
:param time_frame: :py:class:`~mmif.serialize.annotation.Annotation` instance that holds a time interval annotation (``"@type": ".../TimeFrame/..."``)
|
|
158
|
+
:param as_PIL: return :py:class:`~PIL.Image.Image` instead of :py:class:`~numpy.ndarray`
|
|
159
|
+
:return: frame as a :py:class:`numpy.ndarray` or :py:class:`PIL.Image.Image`
|
|
160
|
+
"""
|
|
161
|
+
video_document = mmif[time_frame.get_property('document')]
|
|
162
|
+
rep_frame_num = get_representative_framenum(mmif, time_frame)
|
|
163
|
+
return extract_frames_as_images(video_document, [rep_frame_num], as_PIL=as_PIL)[0]
|
|
164
|
+
|
|
165
|
+
|
|
128
166
|
def sample_frames(start_frame: int, end_frame: int, sample_rate: float = 1) -> List[int]:
|
|
129
167
|
"""
|
|
130
168
|
Helper function to sample frames from a time interval.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mmif-python
|
|
3
|
+
Version: 1.0.15
|
|
4
|
+
Summary: Python implementation of MultiMedia Interchange Format specification. (https://mmif.clams.ai)
|
|
5
|
+
Home-page: https://mmif.clams.ai
|
|
6
|
+
Author: Brandeis Lab for Linguistics and Computation
|
|
7
|
+
Author-email: admin@clams.ai
|
|
8
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: deepdiff>5
|
|
16
|
+
Requires-Dist: jsonschema
|
|
17
|
+
Provides-Extra: seq
|
|
18
|
+
Requires-Dist: numpy; extra == "seq"
|
|
19
|
+
Provides-Extra: cv
|
|
20
|
+
Requires-Dist: pillow; extra == "cv"
|
|
21
|
+
Requires-Dist: opencv-python; extra == "cv"
|
|
22
|
+
Requires-Dist: ffmpeg-python; extra == "cv"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-pep8; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
27
|
+
Requires-Dist: pytype; extra == "dev"
|
|
28
|
+
|
|
29
|
+
## MultiMedia Interchange Format
|
|
30
|
+
[MMIF](https://mmif.clams.ai) is a JSON(-LD)-based data format designed for transferring annotation data between computational analysis applications in [CLAMS project](https://clams.ai).
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## mmif-python
|
|
34
|
+
`mmif-python` is a Python implementation of the MMIF data format.
|
|
35
|
+
`mmif-python` provides various helper classes and functions to handle MMIF JSON in Python,
|
|
36
|
+
including ;
|
|
37
|
+
|
|
38
|
+
1. de-/serialization of MMIF internal data structures to/from JSON
|
|
39
|
+
2. validation of MMIF JSON
|
|
40
|
+
3. handling of CLAMS vocabulary types
|
|
41
|
+
4. navigation of MMIF object via various "search" methods (e.g. `mmif.get_all_views_contain(vocab_type))`)
|
|
42
|
+
|
|
43
|
+
## For more ...
|
|
44
|
+
* [Version history and patch notes](https://github.com/clamsproject/mmif-python/blob/main/CHANGELOG.md)
|
|
45
|
+
* [MMIF Python API documentation](https://clamsproject.github.io/mmif-python)
|
|
46
|
+
* [MMIF JSON specification and schema](https://clamsproject.github.io/mmif)
|
|
@@ -32,4 +32,8 @@ mmif_python.egg-info/PKG-INFO
|
|
|
32
32
|
mmif_python.egg-info/SOURCES.txt
|
|
33
33
|
mmif_python.egg-info/dependency_links.txt
|
|
34
34
|
mmif_python.egg-info/requires.txt
|
|
35
|
-
mmif_python.egg-info/top_level.txt
|
|
35
|
+
mmif_python.egg-info/top_level.txt
|
|
36
|
+
tests/test_serialize.py
|
|
37
|
+
tests/test_utils.py
|
|
38
|
+
tests/test_versioncompat.py
|
|
39
|
+
tests/test_vocab.py
|