mmif-python 1.0.15__tar.gz → 1.0.16__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 (44) hide show
  1. {mmif_python-1.0.15 → mmif_python-1.0.16}/PKG-INFO +1 -1
  2. mmif_python-1.0.16/VERSION +1 -0
  3. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/serialize/mmif.py +69 -2
  4. mmif_python-1.0.16/mmif/utils/text_document_helper.py +12 -0
  5. mmif_python-1.0.16/mmif/ver/__init__.py +2 -0
  6. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif_python.egg-info/PKG-INFO +1 -1
  7. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif_python.egg-info/SOURCES.txt +1 -0
  8. {mmif_python-1.0.15 → mmif_python-1.0.16}/tests/test_serialize.py +58 -0
  9. {mmif_python-1.0.15 → mmif_python-1.0.16}/tests/test_utils.py +28 -7
  10. mmif_python-1.0.15/VERSION +0 -1
  11. mmif_python-1.0.15/mmif/ver/__init__.py +0 -2
  12. {mmif_python-1.0.15 → mmif_python-1.0.16}/LICENSE +0 -0
  13. {mmif_python-1.0.15 → mmif_python-1.0.16}/MANIFEST.in +0 -0
  14. {mmif_python-1.0.15 → mmif_python-1.0.16}/README.md +0 -0
  15. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/__init__.py +0 -0
  16. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/res/__init__.py +0 -0
  17. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/res/clams.vocabulary.yaml +0 -0
  18. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/res/do-not-edit.txt +0 -0
  19. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/res/mmif.json +0 -0
  20. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/serialize/__init__.py +0 -0
  21. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/serialize/annotation.py +0 -0
  22. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/serialize/model.py +0 -0
  23. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/serialize/view.py +0 -0
  24. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/utils/__init__.py +0 -0
  25. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/utils/sequence_helper.py +0 -0
  26. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/utils/timeunit_helper.py +0 -0
  27. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/utils/video_document_helper.py +0 -0
  28. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/ver/do-not-edit.txt +0 -0
  29. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/vocabulary/__init__.py +0 -0
  30. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/vocabulary/annotation_types.py +0 -0
  31. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/vocabulary/base_types.py +0 -0
  32. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/vocabulary/do-not-edit.txt +0 -0
  33. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif/vocabulary/document_types.py +0 -0
  34. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif_docloc_http/__init__.py +0 -0
  35. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif_python.egg-info/dependency_links.txt +0 -0
  36. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif_python.egg-info/requires.txt +0 -0
  37. {mmif_python-1.0.15 → mmif_python-1.0.16}/mmif_python.egg-info/top_level.txt +0 -0
  38. {mmif_python-1.0.15 → mmif_python-1.0.16}/requirements.cv +0 -0
  39. {mmif_python-1.0.15 → mmif_python-1.0.16}/requirements.seq +0 -0
  40. {mmif_python-1.0.15 → mmif_python-1.0.16}/requirements.txt +0 -0
  41. {mmif_python-1.0.15 → mmif_python-1.0.16}/setup.cfg +0 -0
  42. {mmif_python-1.0.15 → mmif_python-1.0.16}/setup.py +0 -0
  43. {mmif_python-1.0.15 → mmif_python-1.0.16}/tests/test_versioncompat.py +0 -0
  44. {mmif_python-1.0.15 → mmif_python-1.0.16}/tests/test_vocab.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mmif-python
3
- Version: 1.0.15
3
+ Version: 1.0.16
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.16
@@ -10,7 +10,7 @@ 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, cast
13
+ from typing import List, Union, Optional, Dict, cast, Iterator, Tuple
14
14
 
15
15
  import jsonschema.validators
16
16
 
@@ -609,7 +609,74 @@ class Mmif(MmifObject):
609
609
  if at_types in view.metadata.contains:
610
610
  return view
611
611
  return None
612
-
612
+
613
+ def _is_in_time_between(self, start: Union[int, float], end: Union[int, float], annotation: Annotation) -> bool:
614
+ s, e = self.get_start(annotation), self.get_end(annotation)
615
+ return (s < start < e) or (s > start and e < end) or (s < end < e)
616
+
617
+ def _handle_time_unit(self, input_unit: str, ann_unit: str,
618
+ start: int, end: int) -> Tuple[Union[int, float, str], Union[int, float, str]]:
619
+ from mmif.utils.timeunit_helper import convert
620
+ start = convert(start, input_unit, ann_unit, 1)
621
+ end = convert(end, input_unit, ann_unit, 1)
622
+ return start, end
623
+
624
+ def get_annotations_between_time(self, start: int, end: int, time_unit: str = "milliseconds") -> Iterator[Annotation]:
625
+ """
626
+ Version: 1.0
627
+ Returns all 'Token' annotations aligned with 'TimeFrame' annotations sorted by start time within start and end time
628
+ Note: this function only works for mmif object obtained from Whisper-wrapper
629
+
630
+ :param start: the start time
631
+ :param end: the end time
632
+ :param time_unit: the time unit, either string "milliseconds" or "seconds", defaults to "milliseconds"
633
+ :return: a generator of 'Token' annotations
634
+ """
635
+ assert start <= end, "Start time must be less than end time"
636
+ assert start >= 0, "Start time must be greater than or equal to zero"
637
+ assert end >= 0, "End time must be greater than or equal to zero"
638
+ # 0. Initialize container and helper method
639
+ valid_tf_anns = []
640
+ tf_to_anns = defaultdict(list)
641
+
642
+ # 1. find all views that contain the type of TF
643
+ views = self.get_all_views_contain([AnnotationTypes.TimeFrame, AnnotationTypes.Alignment])
644
+
645
+ # 2. For each view, extract annotations that satisfy conditions that are TF/TP and fall into time interval
646
+ for view in views:
647
+ # Make sure time unit stay at the same level
648
+ start_time, end_time = self._handle_time_unit(time_unit, view.metadata.contains.get(AnnotationTypes.TimeFrame)["timeUnit"],
649
+ start, end)
650
+ tf_anns = view.get_annotations(at_type=AnnotationTypes.TimeFrame)
651
+ al_anns = view.get_annotations(at_type=AnnotationTypes.Alignment)
652
+
653
+ # Select 'TimeFrame' annotations within given time interval
654
+ for tf in tf_anns:
655
+ if self._is_in_time_between(start_time, end_time, tf):
656
+ valid_tf_anns.append(tf)
657
+
658
+ # Map 'TimeFrame' annotation to its aligned annotation
659
+ for align in al_anns:
660
+ source_id, target_id = align.get_property('source'), align.get_property('target')
661
+ to_long_id = lambda x: x if self.id_delimiter in x else f'{view.id}{self.id_delimiter}{x}'
662
+ try:
663
+ source, target = view.get_annotation_by_id(source_id), view.get_annotation_by_id(target_id)
664
+ if source in valid_tf_anns:
665
+ tf_to_anns[to_long_id(source_id)].append(target)
666
+ elif target in valid_tf_anns:
667
+ tf_to_anns[to_long_id(target_id)].append(source)
668
+ except KeyError:
669
+ pass
670
+
671
+ # 3. For those extracted 'TimeFrame' annotations, sort them by their start time
672
+ sort_tf_anns = sorted(valid_tf_anns, key=lambda x: self.get_start(x))
673
+
674
+ # 4. Yield all annotations aligned with sorted 'TimeFrame' annotations
675
+ for tf_ann in sort_tf_anns:
676
+ anns = tf_to_anns[tf_ann.long_id]
677
+ for ann in anns:
678
+ yield ann
679
+
613
680
  def _get_linear_anchor_point(self, ann: Annotation, targets_sorted=False, start: bool = True) -> Union[int, float]:
614
681
  # TODO (krim @ 2/5/24): Update the return type once timeunits are unified to `ms` as integers (https://github.com/clamsproject/mmif/issues/192)
615
682
  """
@@ -0,0 +1,12 @@
1
+ import mmif
2
+ from mmif import Annotation
3
+
4
+
5
+ def slice_text(mmif_obj, start: int, end: int, unit: str = "milliseconds") -> str:
6
+ token_type = "http://vocab.lappsgrid.org/Token"
7
+ anns_found = mmif_obj.get_annotations_between_time(start, end, unit)
8
+ tokens_sliced = []
9
+ for ann in anns_found:
10
+ if ann.is_type(token_type):
11
+ tokens_sliced.append(ann.get_property('word'))
12
+ return ' '.join(tokens_sliced)
@@ -0,0 +1,2 @@
1
+ __version__ = "1.0.16"
2
+ __specver__ = "1.0.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mmif-python
3
- Version: 1.0.15
3
+ Version: 1.0.16
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
@@ -18,6 +18,7 @@ mmif/serialize/model.py
18
18
  mmif/serialize/view.py
19
19
  mmif/utils/__init__.py
20
20
  mmif/utils/sequence_helper.py
21
+ mmif/utils/text_document_helper.py
21
22
  mmif/utils/timeunit_helper.py
22
23
  mmif/utils/video_document_helper.py
23
24
  mmif/ver/__init__.py
@@ -342,6 +342,64 @@ class TestMmif(unittest.TestCase):
342
342
  self.assertEqual(e_view.id, f'{p}4')
343
343
  self.assertEqual(len(mmif_obj.views), 5)
344
344
 
345
+ def test_get_annotations_between_time(self):
346
+ token_type = "http://vocab.lappsgrid.org/Token"
347
+ # Below tokens are obtained by 'jq' in CLI using command:
348
+ # jq '[
349
+ # .views[3].annotations |
350
+ # .[] |
351
+ # select(."@type"=="http://vocab.lappsgrid.org/Token")] |
352
+ # sort_by(.properties.id | ltrimstr("t") | tonumber) |
353
+ # map(.properties.text)' <examples>.json
354
+ tokens_in_order = ["Hello",
355
+ ",",
356
+ "this",
357
+ "is",
358
+ "Jim",
359
+ "Lehrer",
360
+ "with",
361
+ "the",
362
+ "NewsHour",
363
+ "on",
364
+ "PBS",
365
+ ".",
366
+ "In",
367
+ "the",
368
+ "nineteen",
369
+ "eighties",
370
+ ",",
371
+ "barking",
372
+ "dogs",
373
+ "have",
374
+ "increasingly",
375
+ "become",
376
+ "a",
377
+ "problem",
378
+ "in",
379
+ "urban",
380
+ "areas",
381
+ "."]
382
+ mmif_obj = Mmif(MMIF_EXAMPLES['everything'])
383
+
384
+ # Test case 1: All token annotations are selected
385
+ selected_token_anns = mmif_obj.get_annotations_between_time(0, 22000)
386
+ self.assertEqual(28, len(list(selected_token_anns)))
387
+ for i, ann in enumerate(selected_token_anns):
388
+ self.assertTrue(ann.is_type(token_type))
389
+ self.assertEqual(tokens_in_order[i], ann.get_property("text"))
390
+
391
+ # Test case 2: No token annotation are selected
392
+ selected_token_anns = mmif_obj.get_annotations_between_time(0, 5, time_unit="seconds")
393
+ self.assertEqual(0, len(list(selected_token_anns)))
394
+
395
+ # Test case 3(a): Partial tokens are selected (involve partial overlap)
396
+ selected_token_anns = mmif_obj.get_annotations_between_time(7, 10, time_unit="seconds")
397
+ self.assertEqual(tokens_in_order[3:9], [ann.get_property("text") for ann in selected_token_anns])
398
+
399
+ # Test case 3(b): Partial tokens are selected (only full overlap)
400
+ selected_token_anns = mmif_obj.get_annotations_between_time(11500, 14600)
401
+ self.assertEqual(tokens_in_order[12:17], [ann.get_property("text") for ann in selected_token_anns])
402
+
345
403
  def test_add_document(self):
346
404
  mmif_obj = Mmif(MMIF_EXAMPLES['everything'])
347
405
  med_obj = Document(FRACTIONAL_EXAMPLES['doc_only'])
@@ -6,12 +6,14 @@ from mmif import Mmif, Document, AnnotationTypes
6
6
  from mmif.utils import sequence_helper as sqh
7
7
  from mmif.utils import timeunit_helper as tuh
8
8
  from mmif.utils import video_document_helper as vdh
9
+ from mmif.utils import text_document_helper as tdh
10
+ from mmif.serialize import mmif
11
+ from tests.mmif_examples import *
9
12
 
10
13
 
11
14
  class TestTimeunitHelper(unittest.TestCase):
12
-
13
15
  FPS = 30
14
-
16
+
15
17
  def test_convert(self):
16
18
  self.assertEqual(1000, tuh.convert(1, 's', 'ms', self.FPS))
17
19
  self.assertEqual(1.1, tuh.convert(1100, 'ms', 's', self.FPS))
@@ -35,7 +37,7 @@ class TestVideoDocumentHelper(unittest.TestCase):
35
37
  })
36
38
  self.video_doc.add_property('fps', self.fps)
37
39
  self.mmif_obj.add_document(self.video_doc)
38
-
40
+
39
41
  def test_extract_mid_frame(self):
40
42
  tf = self.a_view.new_annotation(AnnotationTypes.TimeFrame, start=100, end=200, timeUnit='frame', document='d1')
41
43
  self.assertEqual(150, vdh.get_mid_framenum(self.mmif_obj, tf))
@@ -92,11 +94,12 @@ class TestVideoDocumentHelper(unittest.TestCase):
92
94
  s_frame = vdh.second_to_framenum(self.video_doc, 3)
93
95
  e_frame = vdh.second_to_framenum(self.video_doc, 5)
94
96
  self.assertEqual(1, len(vdh.sample_frames(s_frame, e_frame, 60)))
95
-
97
+
96
98
  def test_convert_timepoint(self):
97
- timepoint_ann = self.a_view.new_annotation(AnnotationTypes.BoundingBox, timePoint=3, timeUnit='second', document='d1')
99
+ timepoint_ann = self.a_view.new_annotation(AnnotationTypes.BoundingBox, timePoint=3, timeUnit='second',
100
+ document='d1')
98
101
  self.assertEqual(vdh.convert(3, 's', 'f', self.fps), vdh.convert_timepoint(self.mmif_obj, timepoint_ann, 'f'))
99
-
102
+
100
103
  def test_convert_timeframe(self):
101
104
  self.a_view.metadata.new_contain(AnnotationTypes.TimeFrame, timeUnit='frame', document='d1')
102
105
  timeframe_ann = self.a_view.new_annotation(AnnotationTypes.TimeFrame, start=100, end=200)
@@ -105,7 +108,7 @@ class TestVideoDocumentHelper(unittest.TestCase):
105
108
 
106
109
 
107
110
  class TestSequenceHelper(unittest.TestCase):
108
-
111
+
109
112
  def test_validate_labelset(self):
110
113
  mmif_obj = Mmif(validate=False)
111
114
  view = mmif_obj.new_view()
@@ -172,5 +175,23 @@ class TestSequenceHelper(unittest.TestCase):
172
175
  sqh.smooth_outlying_short_intervals(scores, 1, 1))
173
176
 
174
177
 
178
+ class TestTextDocHelper(unittest.TestCase):
179
+ mmif_obj = Mmif(MMIF_EXAMPLES['everything'])
180
+
181
+ @pytest.mark.skip("The only valid test cases come from kalbi app which annotates wrong property")
182
+ def test_slice_text(self):
183
+ sliced_text_full_overlap = tdh.slice_text(self.mmif_obj, 11500, 14600)
184
+ sliced_text_partial_overlap = tdh.slice_text(self.mmif_obj, 7, 10, unit="seconds")
185
+ no_sliced_text = tdh.slice_text(self.mmif_obj, 0, 5000)
186
+ full_sliced_text = tdh.slice_text(self.mmif_obj, 0, 22, unit="seconds")
187
+ self.assertEqual("In the nineteen eighties ,", sliced_text_full_overlap)
188
+ self.assertEqual("is Jim Lehrer with the NewsHour", sliced_text_partial_overlap)
189
+ self.assertEqual("", no_sliced_text)
190
+ self.assertEqual(
191
+ "Hello , this is Jim Lehrer with the NewsHour on PBS . "
192
+ "In the nineteen eighties , barking dogs have increasingly become a problem in urban areas .",
193
+ full_sliced_text)
194
+
195
+
175
196
  if __name__ == '__main__':
176
197
  unittest.main()
@@ -1 +0,0 @@
1
- 1.0.15
@@ -1,2 +0,0 @@
1
- __version__ = "1.0.15"
2
- __specver__ = "1.0.4"
File without changes
File without changes
File without changes
File without changes
File without changes