mmif-python 1.0.2__tar.gz → 1.0.7__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.
Files changed (36) hide show
  1. {mmif-python-1.0.2 → mmif-python-1.0.7}/MANIFEST.in +1 -0
  2. {mmif-python-1.0.2/mmif_python.egg-info → mmif-python-1.0.7}/PKG-INFO +2 -1
  3. mmif-python-1.0.7/VERSION +1 -0
  4. mmif-python-1.0.7/mmif/__init__.py +17 -0
  5. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/serialize/annotation.py +25 -5
  6. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/serialize/model.py +1 -5
  7. mmif-python-1.0.7/mmif/utils/__init__.py +1 -0
  8. mmif-python-1.0.7/mmif/utils/video_document_helper.py +208 -0
  9. mmif-python-1.0.7/mmif/ver/__init__.py +2 -0
  10. {mmif-python-1.0.2 → mmif-python-1.0.7/mmif_python.egg-info}/PKG-INFO +2 -1
  11. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif_python.egg-info/SOURCES.txt +3 -0
  12. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif_python.egg-info/requires.txt +5 -0
  13. mmif-python-1.0.7/requirements.cv +3 -0
  14. {mmif-python-1.0.2 → mmif-python-1.0.7}/setup.py +4 -0
  15. mmif-python-1.0.2/VERSION +0 -1
  16. mmif-python-1.0.2/mmif/__init__.py +0 -36
  17. mmif-python-1.0.2/mmif/ver/__init__.py +0 -2
  18. {mmif-python-1.0.2 → mmif-python-1.0.7}/LICENSE +0 -0
  19. {mmif-python-1.0.2 → mmif-python-1.0.7}/README.md +0 -0
  20. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/res/__init__.py +0 -0
  21. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/res/clams.vocabulary.yaml +0 -0
  22. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/res/do-not-edit.txt +0 -0
  23. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/res/mmif.json +0 -0
  24. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/serialize/__init__.py +0 -0
  25. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/serialize/mmif.py +0 -0
  26. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/serialize/view.py +0 -0
  27. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/ver/do-not-edit.txt +0 -0
  28. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/vocabulary/__init__.py +0 -0
  29. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/vocabulary/annotation_types.py +0 -0
  30. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/vocabulary/base_types.py +0 -0
  31. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/vocabulary/do-not-edit.txt +0 -0
  32. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif/vocabulary/document_types.py +0 -0
  33. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif_python.egg-info/dependency_links.txt +0 -0
  34. {mmif-python-1.0.2 → mmif-python-1.0.7}/mmif_python.egg-info/top_level.txt +0 -0
  35. {mmif-python-1.0.2 → mmif-python-1.0.7}/requirements.txt +0 -0
  36. {mmif-python-1.0.2 → mmif-python-1.0.7}/setup.cfg +0 -0
@@ -1,4 +1,5 @@
1
1
  # only copied in sdist and be used when building
2
2
  include ./VERSION
3
3
  include ./requirements.txt
4
+ include ./requirements.cv
4
5
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mmif-python
3
- Version: 1.0.2
3
+ Version: 1.0.7
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
@@ -17,4 +17,5 @@ Classifier: License :: OSI Approved :: Apache Software License
17
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
18
  Requires-Python: >=3.8
19
19
  Description-Content-Type: text/markdown
20
+ Provides-Extra: cv
20
21
  Provides-Extra: dev
@@ -0,0 +1 @@
1
+ 1.0.7
@@ -0,0 +1,17 @@
1
+ import importlib.resources
2
+
3
+ # DO NOT CHANGE THIS ORDER, important to prevent circular imports
4
+ from mmif.ver import __version__
5
+ from mmif.ver import __specver__
6
+ from mmif.vocabulary import *
7
+ from mmif.serialize import *
8
+
9
+ _res_pkg = 'res'
10
+ _ver_pkg = 'ver'
11
+ _vocabulary_pkg = 'vocabulary'
12
+ _schema_res_name = 'mmif.json'
13
+
14
+
15
+ def get_mmif_json_schema():
16
+ # TODO (krim @ 7/14/23): use `as_file` after dropping support for Python 3.8
17
+ return importlib.resources.read_text(f'{__package__}.{_res_pkg}', _schema_res_name)
@@ -8,6 +8,7 @@ of a view. For documentation on how views are represented, see
8
8
  """
9
9
  import importlib
10
10
  import itertools
11
+ import os
11
12
  import pathlib
12
13
  import pkgutil
13
14
  import re
@@ -135,6 +136,13 @@ class Annotation(MmifObject):
135
136
  def __getitem__(self, prop_name: str):
136
137
  return self.get(prop_name)
137
138
 
139
+ def __contains__(self, item):
140
+ try:
141
+ self.get(item)
142
+ return True
143
+ except KeyError:
144
+ return False
145
+
138
146
  def is_document(self):
139
147
  return isinstance(self.at_type, DocumentTypesBase)
140
148
 
@@ -219,7 +227,15 @@ class Document(Annotation):
219
227
  the three properties in a specific order so that the latest value is
220
228
  returned, in case there are multiple values for the same key.
221
229
  """
222
- if prop_name in self._props_temporary:
230
+ if prop_name == 'id':
231
+ # because all three dicts have `id` key as required field, we need
232
+ # this special case to return the correct value from the correct dict
233
+ return self.id
234
+ elif prop_name == 'location':
235
+ # because location is internally stored in self.location_,
236
+ # it doesn't work with regular __getitem__ method
237
+ return self.location
238
+ elif prop_name in self._props_temporary:
223
239
  return self._props_temporary[prop_name]
224
240
  elif prop_name in self._props_existing:
225
241
  return self._props_existing[prop_name]
@@ -378,7 +394,7 @@ class DocumentProperties(AnnotationProperties):
378
394
  if "location_" in serialized:
379
395
  serialized["location"] = serialized.pop("location_")
380
396
  return serialized
381
-
397
+
382
398
  @property
383
399
  def text_language(self) -> str:
384
400
  return self.text.lang
@@ -438,7 +454,7 @@ class DocumentProperties(AnnotationProperties):
438
454
  warnings.warn('location_path() is deprecated. Use location_path_resolved() instead.', DeprecationWarning)
439
455
  return self.location_path_resolved()
440
456
 
441
- def location_path_resolved(self) -> Optional[str]:
457
+ def location_path_resolved(self, nonexist_ok=True) -> Optional[str]:
442
458
  """
443
459
  Retrieves only path name of the document location (hostname is ignored),
444
460
  and then try to resolve the path name in the local file system.
@@ -452,11 +468,15 @@ class DocumentProperties(AnnotationProperties):
452
468
  return None
453
469
  scheme = self.location_scheme()
454
470
  if scheme in ('', 'file'):
455
- return urlparse(self.location).path
471
+ p = urlparse(self.location).path
456
472
  elif scheme in discovered_docloc_plugins:
457
- return discovered_docloc_plugins[scheme].resolve(self.location)
473
+ p = discovered_docloc_plugins[scheme].resolve(self.location)
458
474
  else:
459
475
  raise ValueError(f'Cannot resolve location of scheme "{scheme}". Interested in developing mmif-locdoc-{scheme} plugin? See https://clams.ai/mmif-python/plugins')
476
+ if not nonexist_ok and not os.path.exists(p):
477
+ raise FileNotFoundError(f'Cannot find file "{p}"')
478
+ else:
479
+ return p
460
480
 
461
481
  def location_path_literal(self) -> Optional[str]:
462
482
  """
@@ -11,15 +11,13 @@ core functionality for deserializing MMIF JSON data into live objects
11
11
  and serializing live objects into MMIF JSON data. Specialized behavior
12
12
  for the different components of MMIF is added in the subclasses.
13
13
  """
14
+
14
15
  import json
15
16
  from datetime import datetime
16
- from types import MethodType
17
17
  from typing import Union, Any, Dict, Optional, TypeVar, Generic, Generator, Iterator, Type, Set
18
18
 
19
19
  from deepdiff import DeepDiff
20
20
 
21
- import mmif
22
-
23
21
  T = TypeVar('T')
24
22
  S = TypeVar('S')
25
23
  JSON_PRMTV_TYPES: Type = Union[str, int, float, bool, None]
@@ -105,8 +103,6 @@ class MmifObject(object):
105
103
  self._unnamed_attributes = {}
106
104
  if mmif_obj is not None:
107
105
  self.deserialize(mmif_obj)
108
- for method in mmif.patches[self.__class__]:
109
- setattr(self, method.__name__, MethodType(method, self))
110
106
 
111
107
  def disallow_additional_properties(self) -> None:
112
108
  """
@@ -0,0 +1 @@
1
+ from mmif.utils import video_document_helper
@@ -0,0 +1,208 @@
1
+ import importlib
2
+ import warnings
3
+ from typing import List, Union, Tuple
4
+
5
+ import mmif
6
+ from mmif import Annotation, Document, Mmif
7
+ from mmif.vocabulary import DocumentTypes
8
+
9
+ for cv_dep in ('cv2', 'ffmpeg', 'PIL'):
10
+ try:
11
+ importlib.__import__(cv_dep)
12
+ except ImportError as e:
13
+ warnings.warn(f"Optional package \"{e.name}\" is not found. "
14
+ f"You might want to install Computer-Vision dependencies "
15
+ f"by running `pip install mmif-python[cv]=={mmif.__version__}`")
16
+
17
+
18
+ FPS_DOCPROP_KEY = 'fps'
19
+ DURATION_DOCPROP_KEY = 'duration'
20
+ DURATIONUNIT_DOCPROP_KEY = 'durationTimeUnit'
21
+
22
+ UNIT_NORMALIZATION = {
23
+ 'm': 'millisecond',
24
+ 'ms': 'millisecond',
25
+ 'msec': 'millisecond',
26
+ 'millisecond': 'millisecond',
27
+ 'milliseconds': 'millisecond',
28
+ 's': 'second',
29
+ 'se': 'second',
30
+ 'sec': 'second',
31
+ 'second': 'second',
32
+ 'seconds': 'second',
33
+ 'f': 'frame',
34
+ 'fr': 'frame',
35
+ 'frame': 'frame',
36
+ 'frames': 'frame',
37
+ }
38
+
39
+
40
+ def capture(vd: Document):
41
+ import cv2 # pytype: disable=import-error
42
+ if vd is None or vd.at_type != DocumentTypes.VideoDocument:
43
+ raise ValueError(f'The document does not exist.')
44
+
45
+ v = cv2.VideoCapture(vd.location_path())
46
+ vd.add_property(FPS_DOCPROP_KEY, v.get(cv2.CAP_PROP_FPS))
47
+ vd.add_property(DURATION_DOCPROP_KEY, v.get(cv2.CAP_PROP_FRAME_COUNT))
48
+ vd.add_property(DURATIONUNIT_DOCPROP_KEY, 'frames')
49
+ return v
50
+
51
+
52
+ def get_framerate(vd: Document) -> float:
53
+ if vd is None or vd.at_type != DocumentTypes.VideoDocument:
54
+ raise ValueError(f'The document does not exist.')
55
+
56
+ framerate_keys = (FPS_DOCPROP_KEY, 'framerate')
57
+ for k in framerate_keys:
58
+ if k in vd:
59
+ fps = vd.get_property(k)
60
+ return fps
61
+ capture(vd)
62
+ return vd.get_property(FPS_DOCPROP_KEY)
63
+
64
+
65
+ def extract_frames_as_images(vd: Document, framenums: List[int], as_PIL: bool = False):
66
+ """
67
+ Extracts frames from a video document as a list of numpy arrays.
68
+ Use `sample_frames` function in this module to get the list of frame numbers first.
69
+
70
+ :param vd: VideoDocument object that holds the video file location
71
+ :param framenums: integers representing the frame numbers to extract
72
+ :param as_PIL: use PIL.Image instead of numpy.ndarray
73
+ :return: frames as a list of numpy arrays or PIL.Image objects
74
+ """
75
+ import cv2 # pytype: disable=import-error
76
+ if as_PIL:
77
+ from PIL import Image
78
+ frames = []
79
+ video = capture(vd)
80
+ for framenum in framenums:
81
+ video.set(cv2.CAP_PROP_POS_FRAMES, framenum)
82
+ ret, frame = video.read()
83
+ if ret:
84
+ frames.append(Image.fromarray(frame[:, :, ::-1]) if as_PIL else frame)
85
+ else:
86
+ break
87
+ return frames
88
+
89
+
90
+ def get_mid_framenum(mmif: Mmif, tf: Annotation):
91
+ """
92
+ Extracts the middle frame from a video document
93
+ """
94
+ timeunit = get_annotation_property(mmif, tf, 'timeUnit')
95
+ vd = mmif[get_annotation_property(mmif, tf, 'document')]
96
+ fps = get_framerate(vd)
97
+ return sum(convert(float(tf.get_property(timepoint_propkey)), timeunit, 'frame', fps) for timepoint_propkey in ('start', 'end')) // 2
98
+
99
+
100
+ def extract_mid_frame(mmif: Mmif, tf: Annotation, as_PIL: bool = False):
101
+ vd = mmif[get_annotation_property(mmif, tf, 'document')]
102
+ return extract_frames_as_images(vd, [get_mid_framenum(mmif, tf)], as_PIL=as_PIL)[0]
103
+
104
+
105
+ def sample_frames(start_frame: int, end_frame: int, sample_ratio: int = 1) -> List[int]:
106
+ """
107
+ Helper function to sample frames from a time interval.
108
+ When start_frame is 0 and end_frame is X, this function basically works as "cutoff".
109
+
110
+ :param start_frame: start frame of the interval
111
+ :param end_frame: end frame of the interval
112
+ :param sample_ratio: sample ratio or sample step, default is 1, meaning all consecutive frames are sampled
113
+ """
114
+ sample_ratio = int(sample_ratio)
115
+ if sample_ratio < 1:
116
+ raise ValueError(f"Sample ratio must be greater than 1, but got {sample_ratio}")
117
+ frame_nums: List[int] = []
118
+ for i in range(start_frame, end_frame, sample_ratio):
119
+ frame_nums.append(i)
120
+ return frame_nums
121
+
122
+
123
+ def convert(time: Union[int, float], in_unit: str, out_unit: str, fps: float) -> Union[int, float]:
124
+ try:
125
+ in_unit = UNIT_NORMALIZATION[in_unit]
126
+ except KeyError:
127
+ raise ValueError(f"Not supported time unit: {in_unit}")
128
+ try:
129
+ out_unit = UNIT_NORMALIZATION[out_unit]
130
+ except KeyError:
131
+ raise ValueError(f"Not supported time unit: {out_unit}")
132
+ # s>s, ms>ms, f>f
133
+ if in_unit == out_unit:
134
+ return time
135
+ elif out_unit == 'frame':
136
+ # ms>f
137
+ if 'millisecond' == in_unit:
138
+ return int(time / 1000 * fps)
139
+ # s>f
140
+ elif 'second' == in_unit:
141
+ return int(time * fps)
142
+ # s>ms
143
+ elif in_unit == 'second':
144
+ return time * 1000
145
+ # ms>s
146
+ elif in_unit == 'millisecond':
147
+ return time // 1000
148
+ # f>ms, f>s
149
+ else:
150
+ return (time / fps) if out_unit == 'second' else (time / fps * 1000) # pytype: disable=bad-return-type
151
+
152
+ def get_annotation_property(mmif, annotation, prop_name):
153
+ # TODO (krim @ 7/18/23): this probably should be merged to the main mmif.serialize packge
154
+ if prop_name in annotation:
155
+ return annotation.get_property(prop_name)
156
+ try:
157
+ return mmif[annotation.parent].metadata.contains[annotation.at_type][prop_name]
158
+ except KeyError:
159
+ raise KeyError(f"Annotation {annotation.id} does not have {prop_name} property.")
160
+
161
+ def convert_timepoint(mmif: Mmif, timepoint: Annotation, out_unit: str) -> Union[int, float]:
162
+ """
163
+ Converts a time point included in an annotation to a different time unit.
164
+ The input annotation must have ``timePoint`` property.
165
+
166
+ :param mmif: input MMIF to obtain fps and input timeunit
167
+ :param timepoint: annotation with ``timePoint`` property
168
+ :param out_unit: time unit to which the point is converted
169
+ :return: frame number (integer) or second/millisecond (float) of input timepoint
170
+ """
171
+ in_unit = get_annotation_property(mmif, timepoint, 'timeUnit')
172
+ vd = mmif[get_annotation_property(mmif, timepoint, 'document')]
173
+ return convert(timepoint.get_property('timePoint'), in_unit, out_unit, get_framerate(vd))
174
+
175
+ def convert_timeframe(mmif: Mmif, timeframe: Annotation, out_unit: str) -> Union[Tuple[int, int], Tuple[float, float]]:
176
+ """
177
+ Converts start and end points in a TimeFrame annotation a different time unit.
178
+
179
+ :param mmif: input MMIF to obtain fps and input timeunit
180
+ :param timeframe: ``TimeFrame` type annotation
181
+ :param out_unit: time unit to which the point is converted
182
+ :return: tuple of frame numbers (integer) or seconds/milliseconds (float) of input start and end
183
+ """
184
+ in_unit = get_annotation_property(mmif, timeframe, 'timeUnit')
185
+ vd = mmif[get_annotation_property(mmif, timeframe, 'document')]
186
+ return convert(timeframe.get_property('start'), in_unit, out_unit, get_framerate(vd)), \
187
+ convert(timeframe.get_property('end'), in_unit, out_unit, get_framerate(vd))
188
+
189
+
190
+
191
+ def framenum_to_second(video_doc: Document, frame: int):
192
+ fps = get_framerate(video_doc)
193
+ return convert(frame, 'f', 's', fps)
194
+
195
+
196
+ def framenum_to_millisecond(video_doc: Document, frame: int):
197
+ fps = get_framerate(video_doc)
198
+ return convert(frame, 'f', 'ms', fps)
199
+
200
+
201
+ def second_to_framenum(video_doc: Document, second) -> int:
202
+ fps = get_framerate(video_doc)
203
+ return int(convert(second, 's', 'f', fps))
204
+
205
+
206
+ def millisecond_to_framenum(video_doc: Document, millisecond: float) -> int:
207
+ fps = get_framerate(video_doc)
208
+ return int(convert(millisecond, 'ms', 'f', fps))
@@ -0,0 +1,2 @@
1
+ __version__ = "1.0.7"
2
+ __specver__ = "1.0.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mmif-python
3
- Version: 1.0.2
3
+ Version: 1.0.7
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
@@ -17,4 +17,5 @@ Classifier: License :: OSI Approved :: Apache Software License
17
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
18
  Requires-Python: >=3.8
19
19
  Description-Content-Type: text/markdown
20
+ Provides-Extra: cv
20
21
  Provides-Extra: dev
@@ -3,6 +3,7 @@ MANIFEST.in
3
3
  README.md
4
4
  setup.py
5
5
  ./VERSION
6
+ ./requirements.cv
6
7
  ./requirements.txt
7
8
  mmif/__init__.py
8
9
  mmif/res/__init__.py
@@ -14,6 +15,8 @@ mmif/serialize/annotation.py
14
15
  mmif/serialize/mmif.py
15
16
  mmif/serialize/model.py
16
17
  mmif/serialize/view.py
18
+ mmif/utils/__init__.py
19
+ mmif/utils/video_document_helper.py
17
20
  mmif/ver/__init__.py
18
21
  mmif/ver/do-not-edit.txt
19
22
  mmif/vocabulary/__init__.py
@@ -1,6 +1,11 @@
1
1
  deepdiff>5
2
2
  jsonschema
3
3
 
4
+ [cv]
5
+ pillow
6
+ opencv-python
7
+ ffmpeg-python
8
+
4
9
  [dev]
5
10
  pytest
6
11
  pytest-pep8
@@ -0,0 +1,3 @@
1
+ pillow
2
+ opencv-python
3
+ ffmpeg-python
@@ -269,6 +269,9 @@ with open('README.md') as readme:
269
269
  with open('requirements.txt') as requirements:
270
270
  requires = requirements.readlines()
271
271
 
272
+ with open('requirements.cv') as requirements:
273
+ cv_requires = requirements.readlines()
274
+
272
275
  setuptools.setup(
273
276
  name=name,
274
277
  version=version,
@@ -287,6 +290,7 @@ setuptools.setup(
287
290
  },
288
291
  install_requires=requires,
289
292
  extras_require={
293
+ 'cv': cv_requires,
290
294
  'dev': [
291
295
  'pytest',
292
296
  'pytest-pep8',
mmif-python-1.0.2/VERSION DELETED
@@ -1 +0,0 @@
1
- 1.0.2
@@ -1,36 +0,0 @@
1
- import importlib
2
- import pkgutil
3
- import re
4
- from collections import defaultdict
5
- from typing import Dict, Type, Callable, Set
6
-
7
- import pkg_resources
8
-
9
- # DO NOT CHANGE THIS ORDER, important to prevent circular imports
10
- from mmif.ver import __version__
11
- from mmif.ver import __specver__
12
- from mmif.vocabulary import *
13
- from mmif.serialize import *
14
-
15
- _res_pkg = 'res'
16
- _ver_pkg = 'ver'
17
- _vocabulary_pkg = 'vocabulary'
18
- _schema_res_name = 'mmif.json'
19
-
20
-
21
- def get_mmif_json_schema():
22
- res = pkg_resources.resource_stream(f'{__name__}.{_res_pkg}', _schema_res_name)
23
- res_str = res.read().decode('utf-8')
24
- res.close()
25
- return res_str
26
-
27
-
28
- patches: Dict[Type, Set[Callable]] = defaultdict(set)
29
- for _, name, ispkg in pkgutil.iter_modules():
30
- if ispkg and re.match(r'mmif[-_]utils[-_]', name):
31
- mod = importlib.import_module(name)
32
- for c, ms in mod.patches.items():
33
- for m in ms:
34
- if m in patches[c]:
35
- raise ValueError(f'Patch for {c}::{m.__name__} already exists.')
36
- patches[c].add(m)
@@ -1,2 +0,0 @@
1
- __version__ = "1.0.2"
2
- __specver__ = "1.0.0"
File without changes
File without changes
File without changes