mmif-python 1.0.10__tar.gz → 1.0.12__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 (41) hide show
  1. {mmif-python-1.0.10 → mmif-python-1.0.12}/MANIFEST.in +1 -0
  2. {mmif-python-1.0.10/mmif_python.egg-info → mmif-python-1.0.12}/PKG-INFO +17 -1
  3. mmif-python-1.0.12/README.md +18 -0
  4. mmif-python-1.0.12/VERSION +1 -0
  5. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/res/clams.vocabulary.yaml +5 -0
  6. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/res/mmif.json +3 -0
  7. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/serialize/annotation.py +9 -9
  8. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/serialize/mmif.py +29 -12
  9. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/serialize/model.py +2 -2
  10. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/serialize/view.py +40 -7
  11. mmif-python-1.0.12/mmif/utils/sequence_helper.py +292 -0
  12. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/utils/timeunit_helper.py +2 -2
  13. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/utils/video_document_helper.py +12 -8
  14. mmif-python-1.0.12/mmif/ver/__init__.py +2 -0
  15. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/vocabulary/__init__.py +1 -1
  16. mmif-python-1.0.12/mmif/vocabulary/annotation_types.py +43 -0
  17. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/vocabulary/base_types.py +2 -2
  18. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/vocabulary/document_types.py +3 -3
  19. {mmif-python-1.0.10 → mmif-python-1.0.12/mmif_python.egg-info}/PKG-INFO +17 -1
  20. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif_python.egg-info/SOURCES.txt +2 -0
  21. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif_python.egg-info/requires.txt +3 -0
  22. mmif-python-1.0.12/requirements.seq +1 -0
  23. {mmif-python-1.0.10 → mmif-python-1.0.12}/setup.py +6 -2
  24. mmif-python-1.0.10/README.md +0 -3
  25. mmif-python-1.0.10/VERSION +0 -1
  26. mmif-python-1.0.10/mmif/ver/__init__.py +0 -2
  27. mmif-python-1.0.10/mmif/vocabulary/annotation_types.py +0 -33
  28. {mmif-python-1.0.10 → mmif-python-1.0.12}/LICENSE +0 -0
  29. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/__init__.py +0 -0
  30. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/res/__init__.py +0 -0
  31. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/res/do-not-edit.txt +0 -0
  32. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/serialize/__init__.py +0 -0
  33. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/utils/__init__.py +0 -0
  34. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/ver/do-not-edit.txt +0 -0
  35. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif/vocabulary/do-not-edit.txt +0 -0
  36. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif_docloc_http/__init__.py +0 -0
  37. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif_python.egg-info/dependency_links.txt +0 -0
  38. {mmif-python-1.0.10 → mmif-python-1.0.12}/mmif_python.egg-info/top_level.txt +0 -0
  39. {mmif-python-1.0.10 → mmif-python-1.0.12}/requirements.cv +0 -0
  40. {mmif-python-1.0.10 → mmif-python-1.0.12}/requirements.txt +0 -0
  41. {mmif-python-1.0.10 → mmif-python-1.0.12}/setup.cfg +0 -0
@@ -2,4 +2,5 @@
2
2
  include ./VERSION
3
3
  include ./requirements.txt
4
4
  include ./requirements.cv
5
+ include ./requirements.seq
5
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mmif-python
3
- Version: 1.0.10
3
+ Version: 1.0.12
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
@@ -10,6 +10,21 @@ Description: ## MultiMedia Interchange Format
10
10
  [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).
11
11
 
12
12
 
13
+ ## mmif-python
14
+ `mmif-python` is a Python implementation of the MMIF data format.
15
+ `mmif-python` provides various helper classes and functions to handle MMIF JSON in Python,
16
+ including ;
17
+
18
+ 1. de-/serialization of MMIF internal data structures to/from JSON
19
+ 2. validation of MMIF JSON
20
+ 3. handling of CLAMS vocabulary types
21
+ 4. navigation of MMIF object via various "search" methods (e.g. `mmif.get_all_views_contain(vocab_type))`)
22
+
23
+ ## For more ...
24
+ * [Version history and patch notes](https://github.com/clamsproject/mmif-python/blob/main/CHANGELOG.md)
25
+ * [MMIF Python API documentation](https://clamsproject.github.io/mmif-python)
26
+ * [MMIF JSON specification and schema](https://clamsproject.github.io/mmif)
27
+
13
28
  Platform: UNKNOWN
14
29
  Classifier: Development Status :: 2 - Pre-Alpha
15
30
  Classifier: Intended Audience :: Developers
@@ -17,5 +32,6 @@ Classifier: License :: OSI Approved :: Apache Software License
17
32
  Classifier: Programming Language :: Python :: 3 :: Only
18
33
  Requires-Python: >=3.8
19
34
  Description-Content-Type: text/markdown
35
+ Provides-Extra: seq
20
36
  Provides-Extra: cv
21
37
  Provides-Extra: dev
@@ -0,0 +1,18 @@
1
+ ## MultiMedia Interchange Format
2
+ [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).
3
+
4
+
5
+ ## mmif-python
6
+ `mmif-python` is a Python implementation of the MMIF data format.
7
+ `mmif-python` provides various helper classes and functions to handle MMIF JSON in Python,
8
+ including ;
9
+
10
+ 1. de-/serialization of MMIF internal data structures to/from JSON
11
+ 2. validation of MMIF JSON
12
+ 3. handling of CLAMS vocabulary types
13
+ 4. navigation of MMIF object via various "search" methods (e.g. `mmif.get_all_views_contain(vocab_type))`)
14
+
15
+ ## For more ...
16
+ * [Version history and patch notes](https://github.com/clamsproject/mmif-python/blob/main/CHANGELOG.md)
17
+ * [MMIF Python API documentation](https://clamsproject.github.io/mmif-python)
18
+ * [MMIF JSON specification and schema](https://clamsproject.github.io/mmif)
@@ -0,0 +1 @@
1
+ 1.0.12
@@ -78,6 +78,11 @@ properties:
78
78
  <br><br>
79
79
  [Note] Annotations from a classifier app must have this property.
80
80
  classifications:
81
+ type: Map from String to Number
82
+ description: >-
83
+ Alias for the <code>classification</code> metadata. Here for historical
84
+ reasons.
85
+ classification:
81
86
  type: Map from String to Number
82
87
  description: >-
83
88
  A map from label values to their "score" numbers provided by a classifier.
@@ -95,6 +95,9 @@
95
95
  },
96
96
  "parameters": {
97
97
  "$ref": "#/definitions/strStrMap"
98
+ },
99
+ "appConfiguration": {
100
+ "type": "object"
98
101
  }
99
102
  },
100
103
  "oneOf": [
@@ -18,16 +18,16 @@ from typing import Union, Dict, List, Optional, Iterator, MutableMapping, TypeVa
18
18
  from urllib.parse import urlparse
19
19
 
20
20
  from mmif.vocabulary import ThingTypesBase, DocumentTypesBase
21
- from .model import MmifObject, JSON_PRMTV_TYPES
21
+ from .model import MmifObject, PRMTV_TYPES
22
22
  from .. import DocumentTypes, AnnotationTypes
23
23
  import mmif_docloc_http
24
24
 
25
25
  __all__ = ['Annotation', 'AnnotationProperties', 'Document', 'DocumentProperties', 'Text']
26
26
 
27
27
  T = TypeVar('T')
28
- LIST_PRMTV = typing.List[JSON_PRMTV_TYPES] # list of values (most cases for annotation props)
28
+ LIST_PRMTV = typing.List[PRMTV_TYPES] # list of values (most cases for annotation props)
29
29
  LIST_LIST_PRMTV = typing.List[LIST_PRMTV] # list of list of values (e.g. for coordinates)
30
- DICT_PRMTV = typing.Dict[str, JSON_PRMTV_TYPES] # dict of values (`text` prop of `TextDocument` and other complex props)
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
 
33
33
 
@@ -77,7 +77,7 @@ class Annotation(MmifObject):
77
77
  subtypes, and effectively deprecated `frameType` and `boxType`
78
78
  in `TimeFrame` and `BoundingBox` respectively.
79
79
  """
80
- prop_aliases = AnnotationTypes.prop_aliases.get(self._type.shortname, {})
80
+ prop_aliases = AnnotationTypes._prop_aliases.get(self._type.shortname, {})
81
81
  for alias_reprep, alias_group in prop_aliases.items():
82
82
  if key_to_add in alias_group:
83
83
  for alias in alias_group:
@@ -129,10 +129,10 @@ class Annotation(MmifObject):
129
129
 
130
130
  @staticmethod
131
131
  def check_prop_value_is_simple_enough(
132
- value: Union[JSON_PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]) -> bool:
132
+ value: Union[PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]) -> bool:
133
133
 
134
134
  def json_primitives(x):
135
- return isinstance(x, typing.get_args(JSON_PRMTV_TYPES))
135
+ return isinstance(x, typing.get_args(PRMTV_TYPES))
136
136
 
137
137
  def json_primitives_list(x):
138
138
  return isinstance(x, list) and all(map(json_primitives, x))
@@ -146,7 +146,7 @@ class Annotation(MmifObject):
146
146
  or (isinstance(value, dict) and all(map(lambda x: isinstance(x[0], str) and (json_primitives(x[1]) or json_primitives_list(x[1])), value.items())))
147
147
 
148
148
  def add_property(self, name: str,
149
- value: Union[JSON_PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]) -> None:
149
+ value: Union[PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]) -> None:
150
150
  """
151
151
  Adds a property to the annotation's properties.
152
152
  :param name: the name of the property
@@ -162,7 +162,7 @@ class Annotation(MmifObject):
162
162
  # f"(\"{name}\": \"{str(value)}\"")
163
163
  self._add_prop_aliases(name, value)
164
164
 
165
- def get(self, prop_name: str) -> Union['AnnotationProperties', JSON_PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]:
165
+ def get(self, prop_name: str) -> Union['AnnotationProperties', PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]:
166
166
  """
167
167
  A special getter for Annotation properties. This is to allow for
168
168
  directly accessing properties without having to go through the
@@ -242,7 +242,7 @@ class Document(Annotation):
242
242
  super().__init__(doc_obj)
243
243
 
244
244
  def add_property(self, name: str,
245
- value: Union[JSON_PRMTV_TYPES, LIST_PRMTV]
245
+ value: Union[PRMTV_TYPES, LIST_PRMTV]
246
246
  ) -> None:
247
247
  """
248
248
  Adds a property to the document's properties.
@@ -6,6 +6,7 @@ See the specification docs and the JSON Schema file for more information.
6
6
  """
7
7
 
8
8
  import json
9
+ import math
9
10
  import warnings
10
11
  from collections import defaultdict
11
12
  from datetime import datetime
@@ -123,12 +124,20 @@ class Mmif(MmifObject):
123
124
  # then, do the same to associated Document objects. Note that,
124
125
  # in a view, it is guaranteed that all Annotation objects are not duplicates
125
126
  if ann.at_type == AnnotationTypes.Annotation:
127
+ doc_id = ann.get_property('document')
126
128
  try:
127
- doc_id = ann.get_property('document')
128
129
  for prop_key, prop_value in ann.properties.items():
129
130
  self.get_document_by_id(doc_id)._props_ephemeral[prop_key] = prop_value
130
131
  except KeyError:
131
- warnings.warn(f"Annotation {ann.id} has a document ID {doc_id} that does not exist in the MMIF object. Skipping.", RuntimeWarning)
132
+ warnings.warn(f"Annotation {ann.id} (in view {view.id}) has a document ID {doc_id} that "
133
+ f"does not exist in the MMIF object. Skipping.", RuntimeWarning)
134
+ # lastly, add quick access to `start` and `end` values if the annotation is using `targets` property
135
+ if 'targets' in ann.properties:
136
+ if 'start' in ann.properties or 'end' in ann.properties:
137
+ raise ValueError(f"Annotation {ann.id} (in view {view.id}) has `targes` and `start`/`end/` "
138
+ f"properties at the same time. Annotation anchors are ambiguous.")
139
+ ann._props_ephemeral['start'] = self._get_linear_anchor_point(ann, start=True)
140
+ ann._props_ephemeral['end'] = self._get_linear_anchor_point(ann, start=False)
132
141
 
133
142
  def generate_capital_annotations(self):
134
143
  """
@@ -427,7 +436,8 @@ class Mmif(MmifObject):
427
436
  next(annotations)
428
437
  views.append(view)
429
438
  except StopIteration:
430
- # search failed by the full doc_id string, now try trimming the view_id from the string and re-do the search
439
+ # means search failed by the full doc_id string,
440
+ # now try trimming the view_id from the string and re-do the search
431
441
  if Mmif.id_delimiter in doc_id:
432
442
  vid, did = doc_id.split(Mmif.id_delimiter)
433
443
  if view.id == vid:
@@ -479,7 +489,7 @@ class Mmif(MmifObject):
479
489
  return view
480
490
  return None
481
491
 
482
- def _get_linear_anchor_point(self, ann: Annotation, start: bool = True) -> Union[int, float]:
492
+ def _get_linear_anchor_point(self, ann: Annotation, targets_sorted=False, start: bool = True) -> Union[int, float]:
483
493
  # TODO (krim @ 2/5/24): Update the return type once timeunits are unified to `ms` as integers (https://github.com/clamsproject/mmif/issues/192)
484
494
  """
485
495
  Retrieves the anchor point of the annotation. Currently, this method only supports linear anchors,
@@ -487,21 +497,28 @@ class Mmif(MmifObject):
487
497
 
488
498
  :param ann: An Annotation object that has a linear anchor point. Namely, some subtypes of `Region` vocabulary type.
489
499
  :param start: If True, returns the start anchor point. Otherwise, returns the end anchor point. N/A for `timePoint` anchors.
500
+ :param targets_sorted: If True, the method will assume that the targets are sorted in the order of the anchor points.
490
501
  :return: the anchor point of the annotation. 1d for linear regions (time, text)
491
502
  """
492
503
  props = ann.properties
493
504
  if 'timePoint' in props:
494
505
  return ann.get_property('timePoint')
495
506
  elif 'targets' in props:
507
+
508
+ def get_target_ann(cur_ann, target_id):
509
+ if Mmif.id_delimiter not in target_id:
510
+ target_id = Mmif.id_delimiter.join((cur_ann.parent, target_id))
511
+ return self.__getitem__(target_id)
512
+
513
+ if not targets_sorted:
514
+ point = math.inf if start else -1
515
+ comp = min if start else max
516
+ for target_id in ann.get_property('targets'):
517
+ target = get_target_ann(ann, target_id)
518
+ point = comp(point, self._get_linear_anchor_point(target, start=start))
519
+ return point
496
520
  target_id = ann.get_property('targets')[0 if start else -1]
497
- # TODO (krim @ 2/5/24): not sure if this is the correct way to pick the "first" (or "last") target,
498
- # since the targets list is not guaranteed to be sorted.
499
- # However, due the recursive nature of this method, it is likely impossible
500
- # to get all `start` (or `end`) values of the targets recursively and then pick the min (or max) value.
501
- if Mmif.id_delimiter in target_id:
502
- target = self.__getitem__(target_id)
503
- else:
504
- target = self.__getitem__(Mmif.id_delimiter.join((ann.parent, target_id)))
521
+ target = get_target_ann(ann, target_id)
505
522
  return self._get_linear_anchor_point(target, start=start)
506
523
  elif (start and 'start' in props) or (not start and 'end' in props):
507
524
  return ann.get_property('start' if start else 'end')
@@ -20,14 +20,14 @@ from deepdiff import DeepDiff
20
20
 
21
21
  T = TypeVar('T')
22
22
  S = TypeVar('S')
23
- JSON_PRMTV_TYPES: Type = Union[str, int, float, bool, None]
23
+ PRMTV_TYPES: Type = Union[str, int, float, bool, None]
24
24
 
25
25
  __all__ = [
26
26
  'MmifObject',
27
27
  'MmifObjectEncoder',
28
28
  'DataList',
29
29
  'DataDict',
30
- 'JSON_PRMTV_TYPES'
30
+ 'PRMTV_TYPES'
31
31
  ]
32
32
 
33
33
 
@@ -8,6 +8,7 @@ data that was previously present in the MMIF file.
8
8
  from datetime import datetime
9
9
  from typing import Dict, Union, Optional, Generator, List, cast
10
10
 
11
+ from mmif.serialize.model import PRMTV_TYPES
11
12
  from mmif.vocabulary import ThingTypesBase, ClamsTypesBase
12
13
  from .annotation import Annotation, Document
13
14
  from .model import MmifObject, DataList, DataDict
@@ -243,7 +244,8 @@ class ViewMetadata(MmifObject):
243
244
  self.timestamp: Optional[datetime] = None
244
245
  self.app: str = ''
245
246
  self.contains: ContainsDict = ContainsDict()
246
- self.parameters: dict = {}
247
+ self.parameters: Dict[str, str] = {}
248
+ self.appConfiguration: Dict[str, Union[PRMTV_TYPES, List[PRMTV_TYPES]]] = {}
247
249
  self.error: Union[dict, ErrorDict] = {}
248
250
  self.warnings: List[str] = []
249
251
  self._required_attributes = ["app"]
@@ -282,16 +284,43 @@ class ViewMetadata(MmifObject):
282
284
  self.add_contain(new_contain, at_type)
283
285
  return new_contain
284
286
 
285
- def add_contain(self, contain: 'Contain', at_type: Union[str, ThingTypesBase]):
287
+ def add_contain(self, contain: 'Contain', at_type: Union[str, ThingTypesBase]) -> None:
286
288
  self.contains[at_type] = contain
287
289
 
288
- def add_parameters(self, **runtime_params):
289
- self.parameters.update(dict(runtime_params))
290
+ def add_app_configuration(self, config_key: str, config_value: Union[PRMTV_TYPES, List[PRMTV_TYPES]]) -> None:
291
+ """
292
+ Add a configuration key-value pair to the app_configuration dictionary.
293
+ """
294
+ self.appConfiguration[config_key] = config_value
295
+
296
+ def get_app_configuration(self, config_key: str) -> Union[PRMTV_TYPES, List[PRMTV_TYPES]]:
297
+ """
298
+ Get a configuration value from the app_configuration dictionary.
299
+ """
300
+ try:
301
+ return self.appConfiguration[config_key]
302
+ except KeyError:
303
+ raise KeyError(f"app is not configured for \"{config_key}\" key in the view: {self.serialize()}")
290
304
 
291
- def add_parameter(self, param_key, param_value):
305
+ def add_parameters(self, **runtime_params: str):
306
+ """
307
+ Add runtime parameters as a batch (dict) to the view metadata. Note that parameter values must be strings.
308
+ """
309
+ for k, v in runtime_params.items():
310
+ self.add_parameter(k, v)
311
+
312
+ def add_parameter(self, param_key: str, param_value: str):
313
+ """
314
+ Add a single runtime parameter to the view metadata. Note that parameter value must be a string.
315
+ """
316
+ assert isinstance(param_value, str), \
317
+ f"Parameter value must be a string, \"{param_value}\" ({type(param_value)}) is given for key \"{param_key}\"."
292
318
  self.parameters[param_key] = param_value
293
319
 
294
- def get_parameter(self, param_key):
320
+ def get_parameter(self, param_key: str) -> str:
321
+ """
322
+ Get a runtime parameter from the view metadata.
323
+ """
295
324
  try:
296
325
  return self.parameters[param_key]
297
326
  except KeyError:
@@ -384,7 +413,11 @@ class ContainsDict(DataDict[ThingTypesBase, Contain]):
384
413
  return self._items.get(key, default)
385
414
 
386
415
  def __contains__(self, item: Union[str, ThingTypesBase]):
387
- return item in self._items
416
+ if isinstance(item, str):
417
+ string_keys = [str(k) for k in self._items.keys()]
418
+ return item in string_keys
419
+ else:
420
+ return item in self._items
388
421
 
389
422
  def pop(self, key):
390
423
  self._items.pop(key)
@@ -0,0 +1,292 @@
1
+ """
2
+ This module provides helpers for handling sequence labeling. Specifically, it provides
3
+
4
+ * a generalized label re-mapper for "post-binning" of labels
5
+ * conversion from a list of CLAMS annotations (with ``classification`` props) into a list of reals (scores by labels), can be combined with the label re-mapper mentioned above
6
+ * :py:meth:`mmif.utils.sequence_helper.smooth_outlying_short_intervals`: a simple smoothing algorithm by trimming "short" outlier sequences
7
+
8
+ However, it DOES NOT provide
9
+
10
+ * direct conversion between CLAMS annotations. For example, it does not directly handle stitching of ``TimePoint`` into ``TimeFrames``.
11
+ * support for multi-class scenario, such as handling of _competing_ subsequence or overlapping labels.
12
+
13
+ Some functions can use optional external libraries (e.g., ``numpy``) for better performance.
14
+ Hence, if you see a warning about missing optional packages, you might want to install them by running ``pip install mmif-python[seq]``.
15
+ """
16
+
17
+ import importlib
18
+ import itertools
19
+ import warnings
20
+ from typing import List, Tuple, Dict, Union, Iterable, Callable
21
+
22
+ import mmif
23
+ from mmif import Annotation
24
+ from mmif.serialize.model import PRMTV_TYPES
25
+
26
+ for seq_dep in ['numpy']:
27
+ try:
28
+ importlib.__import__(seq_dep)
29
+ except ImportError as e:
30
+ warnings.warn(f"Optional package \"{e.name}\" is not found. "
31
+ f"You might want to install Sequence Helper dependencies "
32
+ f"by running `pip install mmif-python[seq]=={mmif.__version__}`")
33
+
34
+ NEG_LABEL = '-'
35
+
36
+
37
+ def smooth_outlying_short_intervals(scores: List[float],
38
+ min_spseq_size: int,
39
+ min_snseq_size: int,
40
+ min_score: float = 0.5,
41
+ ):
42
+ """
43
+ Given a list of scores, a score threshold, and smoothing parameters,
44
+ identify the intervals of "positive" scores by "trimming" the short
45
+ positive sequences ("spseq") and short negative sequences ("snseq"). To
46
+ decide the positivity, first step is binarization of the scores by the
47
+ ``min_score`` threshold. Given ``Sr`` as "raw" input real-number scores
48
+ list, and ``min_score=0.5``,
49
+
50
+ .. code-block:: javascript
51
+
52
+ Sr: [0.3, 0.6, 0.2, 0.8, 0.2, 0.9, 0.8, 0.5, 0.1, 0.5, 0.8, 0.3, 1.0, 0.7, 0.5, 0.5, 0.5, 0.8, 0.3, 0.6]
53
+
54
+
55
+
56
+ the binarization is done by simply comparing each score to the
57
+ threshold to get ``S`` list of binary scores
58
+
59
+ .. code-block:: javascript
60
+
61
+ 1.0 : |
62
+ 0.9 : | |
63
+ 0.8 : | | | | | |
64
+ 0.7 : | | | | | | |
65
+ 0.6 : | | | | | | | | |
66
+ 0.5 :----+-----+-----+--+--+-----+--+-----+--+--+--+--+--+-----+-
67
+ 0.4 : | | | | | | | | | | | | | |
68
+ 0.3 : | | | | | | | | | | | | | | | | |
69
+ 0.2 : | | | | | | | | | | | | | | | | | | |
70
+ 0.1 : | | | | | | | | | | | | | | | | | | | |
71
+ 0.0 +------------------------------------------------------------
72
+ raw :.3 .6 .2 .8 .2 .9 .8 .5 .1 .5 .8 .3 1. .7 .5 .5 .5 .8 .3 .6
73
+ S : 0 1 0 1 0 1 1 0 0 0 1 0 1 1 0 1 1 1 0 1
74
+
75
+ Note that the size of a positive or negative sequence can be as small
76
+ as 1.
77
+
78
+ Then, here are examples of smoothing a list of binary scores into
79
+ intervals, by trimming "very short" (under thresholds) sequences of
80
+ positive or negative:
81
+
82
+ .. note::
83
+ legends:
84
+
85
+ * ``t`` is unit index (e.g. time index)
86
+ * ``S`` is the list of binary scores (zeros and ones)
87
+ * ``I`` is the list of intervals after smoothing
88
+
89
+ #. with params ``min_spseq_size==1``, ``min_snseq_size==4``
90
+
91
+ .. code-block:: javascript
92
+
93
+ t: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
94
+ S: [0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1]
95
+ I: [0, 1--1--1--1--1--1--1--1--1--1--1--1, 0--0--0--0--0--0, 1]
96
+
97
+ Explanation: ``min_snseq_size`` is used to smooth short sequences
98
+ of negative predictions. In this, zeros from t[7:10] are smoothed
99
+ into "one" I, while zeros from t[13:19] are kept as "zero" I. Note
100
+ that the "short" snseqs at the either ends (t[0:1]) are never
101
+ smoothed.
102
+
103
+ #. with params ``min_spseq_size==4``, ``min_snseq_size==2``
104
+
105
+ .. code-block:: javascript
106
+
107
+ t: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
108
+ S: [0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1]
109
+ I: [0, 1--1--1--1--1--1, 0--0--0--0--0--0--0--0--0--0--0--0--0]
110
+
111
+ Explanation: ``min_spseq_size`` is used to smooth short sequences
112
+ of positive predictions. In this example, the spseqs of ones from
113
+ both t[10:13] and t[19:20] are smoothed. Note that the "short"
114
+ spseqs at the either ends (t[19:20]) are always smoothed.
115
+
116
+ #. with params ``min_spseq_size==4``, ``min_snseq_size==4``
117
+
118
+ .. code-block:: javascript
119
+
120
+ t: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
121
+ S: [0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1]
122
+ I: [0, 1--1--1--1--1--1--1--1--1--1--1--1--0--0--0--0--0--0--0]
123
+
124
+ Explanation: When two threshold parameters are working together,
125
+ the algorithm will prioritize the smoothing of the snseqs over the
126
+ smoothing of the spseqs. Thus, in this example, the snseq t[7:10]
127
+ gets first smoothed "up" before the spseq t[10:13] is smoothed
128
+ "down", resulting in a long final I.
129
+
130
+ #. with params ``min_spseq_size==4``, ``min_snseq_size==4``
131
+
132
+ .. code-block:: javascript
133
+
134
+ t: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
135
+ S: [1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1]
136
+ I: [1--1--1--1--1--1--1, 0--0--0--0, 1--1--1--1--1--1--1--1--1]
137
+
138
+ Explanation: Since smoothing of snseqs is prioritized, short spseqs
139
+ at the beginning or the end can be kept.
140
+
141
+ #. with params ``min_spseq_size==1``, ``min_snseq_size==1``
142
+
143
+ .. code-block:: javascript
144
+
145
+ t: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
146
+ S: [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1]
147
+ I: [0--0--0, 1--1--1--1, 0--0--0--0, 1--1--1--1, 0--0--0, 1--1]
148
+
149
+ Explanation: When both width thresholds are set to 1, the algorithm
150
+ works essentially in the "stitching" only mode.
151
+
152
+ :param scores: **SORTED** list of scores to be smoothed. The score list
153
+ is assumed to be "exhaust" the entire time or space of
154
+ the underlying document segment.
155
+ (Sorted by the start, and then by the end of anchors)
156
+ :param min_score: minimum threshold to use to discard
157
+ low-scored units (strictly less than)
158
+ :param min_spseq_size: minimum size of a positive sequence not to be
159
+ smoothed (greater or equal to)
160
+ :param min_snseq_size: minimum size of a negative sequence not to be
161
+ smoothed (greater or equal to)
162
+ :return: list of tuples of start(inclusive)/end(exclusive) indices
163
+ of the "positive" sequences. Negative sequences (regardless of
164
+ their size) are not included in the output.
165
+ """
166
+
167
+ def trimmer(elems, min_width, target=False, keep_short_ends=False):
168
+ """
169
+ This will loop through a list of bools and convert short ``target``
170
+ subsequences to ¬target (or not-target). A "short" subsequence is
171
+ defined by its size being strictly smaller than ``min_width``.
172
+ When ``always_trim_ends``, the first and the last subsequences are
173
+ always converted to ¬target, regardless of their size.
174
+ """
175
+ try:
176
+ assert min_width > 0
177
+ except AssertionError:
178
+ raise ValueError(f"minimum width threshold must be a positive number, but got {min_width}")
179
+ group_gen = itertools.groupby(elems)
180
+ new_membership = None
181
+ last_membership = None
182
+ last_membersnum = 0
183
+ added = 0
184
+ for i, (positivity, group) in enumerate(group_gen):
185
+ membersnum = len(list(group))
186
+ for _ in range(last_membersnum): # won't be executed for the first group (last_membersnum=0)
187
+ yield new_membership
188
+ added += 1
189
+ new_membership = not target
190
+ # keep target as target if
191
+ # 1. the sequence is wide enough
192
+ # 2. the sequence is at the beginning of the list and "keep_ends"
193
+ if positivity == target and (membersnum >= min_width or (keep_short_ends and i == 0)):
194
+ new_membership = target
195
+ last_membersnum = membersnum
196
+ last_membership = positivity
197
+ # and finally 3. the sequence is at the final of the list and "keep_ends"
198
+ new_membership = not target
199
+ if last_membership == target and (last_membersnum >= min_width or keep_short_ends):
200
+ new_membership = target
201
+ for _ in range(last_membersnum):
202
+ yield new_membership
203
+ added += 1
204
+
205
+ # first pass to smooth short gaps first
206
+ pass1 = trimmer(
207
+ map(lambda x: x >= min_score, scores),
208
+ min_snseq_size, target=False, keep_short_ends=True)
209
+ pass2 = trimmer(
210
+ pass1, min_spseq_size, target=True, keep_short_ends=False)
211
+ return _sequence_to_intervals(pass2)
212
+
213
+
214
+ def _sequence_to_intervals(seq: Iterable[bool]) -> List[Tuple[int, int]]:
215
+ pos_ints = []
216
+ cur = 0
217
+ for positivity, members in itertools.groupby(seq):
218
+ l = len(list(members))
219
+ if positivity:
220
+ pos_ints.append((cur, cur + l))
221
+ cur += l
222
+ return pos_ints
223
+
224
+
225
+ def validate_labelset(annotations: Iterable[Annotation]) -> List[str]:
226
+ """
227
+ Simple check for a list of annotations to see if they have the same label set.
228
+
229
+ :raise: AttributeError if an element in the input list doesn't have the ``labelset`` property
230
+ :raise: ValueError if different ``labelset`` values are found
231
+ :return: a list of the common ``labelset`` value (list of label names)
232
+ """
233
+ # first, grab the label set from the source annotations
234
+ peek, annotations = itertools.tee(annotations)
235
+ try:
236
+ src_labels = set(next(peek).get_property('labelset'))
237
+ except KeyError:
238
+ raise AttributeError("The annotation in the list doesn't have "
239
+ "'labelset' property. Are they annotated by"
240
+ "a classification task?")
241
+
242
+ # and validate that all annotations have the same label set
243
+ for a in annotations:
244
+ if set(a.get_property('labelset')) != src_labels:
245
+ raise ValueError("All annotations must have the same label set, "
246
+ f"but found {a.get_property('labelset')}, "
247
+ f"different from {src_labels}")
248
+ return list(src_labels)
249
+
250
+
251
+ def build_label_remapper(src_labels: List[str], dst_labels: Dict[str, PRMTV_TYPES]) -> Dict[str, PRMTV_TYPES]:
252
+ """
253
+ Build a label remapper dictionary from source and destination labels.
254
+
255
+ :param src_labels: a list of all labels on the source side
256
+ :param dst_labels: a dict from source labels to destination labels.
257
+ Source labels not in this dict will be remapped to a negative label (``-``).
258
+ :return: a dict that exhaustively maps source labels to destination labels
259
+ """
260
+ if len(dst_labels) == 0:
261
+ return dict(zip(src_labels, src_labels))
262
+ else:
263
+ return {**dst_labels, **dict((k, NEG_LABEL) for k in src_labels if k not in dst_labels)}
264
+
265
+
266
+ def build_score_lists(classifications: List[Dict], label_remapper: Dict,
267
+ score_remap_op: Callable[..., float] = max) \
268
+ -> Tuple[Dict[str, int], "numpy.ndarray"]: # pytype: disable=name-error
269
+
270
+ """
271
+ Build lists of scores indexed by the label names.
272
+
273
+ :param classifications: list of dictionaries of classification results, taken from input annotation objects
274
+ :param label_remapper: a dictionary that maps source label names to destination label names (formerly "postbin")
275
+ :param score_remap_op: a function to remap the scores from multiple source labels binned to a destination label
276
+ common choices are ``max``, ``min``, or ``sum``
277
+ :return: 1. a dictionary that maps label names to their index in the score list
278
+ 2. 2-d numpy array of scores, of which rows are indexed by label map dict (first return value)
279
+ """
280
+ import numpy
281
+ scores = {lbl: [] for lbl in label_remapper.values()}
282
+ for c_idx, classification in enumerate(classifications):
283
+ for src_label, src_score in classification.items():
284
+ dst_label = label_remapper[src_label]
285
+ if len(scores[dst_label]) == c_idx: # means this is the first score for this label for this loop iter
286
+ scores[dst_label].append(src_score)
287
+ else:
288
+ scores[dst_label][-1] = score_remap_op((scores[dst_label][-1], src_score))
289
+ label_idx = {label: i for i, label in enumerate(scores.keys())}
290
+ score_lists = list(scores.values())
291
+
292
+ return label_idx, numpy.array(score_lists)
@@ -74,11 +74,11 @@ def convert(t: Union[int, float, str], in_unit: str, out_unit: str, fps: float)
74
74
  return int(t * fps)
75
75
  # s>(ms or i)
76
76
  elif in_unit == 'second':
77
- return t * 1000 if out_unit == 'millisecond' else _second_to_isoformat(t)
77
+ return int(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 (t / fps * 1000) # pytype: disable=bad-return-type
83
+ return (t / fps) if out_unit == 'second' else (round(t / fps, 3) * 1000) # pytype: disable=bad-return-type
84
84
 
@@ -81,15 +81,19 @@ def extract_frames_as_images(video_document: Document, framenums: List[int], as_
81
81
  from PIL import Image
82
82
  frames = []
83
83
  video = capture(video_document)
84
- for framenum in framenums:
85
- if framenum > video_document.get_property(FRAMECOUNT_DOCPROP_KEY):
86
- raise ValueError(f'Frame number {framenum} is greater than the number of frames in the video.')
87
- video.set(cv2.CAP_PROP_POS_FRAMES, framenum-1)
84
+ cur_f = 0
85
+ while True:
86
+ if not framenums or cur_f > video_document.get_property(FRAMECOUNT_DOCPROP_KEY):
87
+ break
88
88
  ret, frame = video.read()
89
- if ret:
89
+ if cur_f == framenums[0]:
90
+ if not ret:
91
+ sec = convert(cur_f, 'f', 's', video_document.get_property(FPS_DOCPROP_KEY))
92
+ warnings.warn(f'Frame #{cur_f} ({sec}s) could not be read from the video {video_document.id}.')
93
+ continue
90
94
  frames.append(Image.fromarray(frame[:, :, ::-1]) if as_PIL else frame)
91
- else:
92
- break
95
+ framenums.pop(0)
96
+ cur_f += 1
93
97
  return frames
94
98
 
95
99
 
@@ -104,7 +108,7 @@ def get_mid_framenum(mmif: Mmif, time_frame: Annotation):
104
108
  timeunit = time_frame.get_property('timeUnit')
105
109
  video_document = mmif[time_frame.get_property('document')]
106
110
  fps = get_framerate(video_document)
107
- return convert(mmif.get_start(time_frame) + mmif.get_end(time_frame), timeunit, 'frame', fps) // 2
111
+ return convert(time_frame.get_property('start') + time_frame.get_property('end'), timeunit, 'frame', fps) // 2
108
112
 
109
113
 
110
114
  def extract_mid_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
@@ -0,0 +1,2 @@
1
+ __version__ = "1.0.12"
2
+ __specver__ = "1.0.4"
@@ -6,4 +6,4 @@ from .base_types import DocumentTypesBase
6
6
  from .annotation_types import AnnotationTypes
7
7
  from .document_types import DocumentTypes
8
8
 
9
- typevers = {**ThingType.typevers, **AnnotationTypes.typevers, **DocumentTypes.typevers}
9
+ _typevers = {**ThingType._typevers, **AnnotationTypes._typevers, **DocumentTypes._typevers}
@@ -0,0 +1,43 @@
1
+ # Spec version 1.0.4
2
+ # This file is auto-generated by setup.py
3
+
4
+ from .base_types import AnnotationTypesBase
5
+
6
+
7
+ class AnnotationTypes(AnnotationTypesBase):
8
+ """
9
+ This class contains the CLAMS annotation types
10
+ defined in the spec version 1.0.4 as class variables.
11
+ """
12
+
13
+ # prpoerty aliases, first added in MMIF 1.0.2
14
+ # map from type short name to a dictionary of property aliases
15
+ # where nested dictionary maps from representative name to its aliases
16
+ _prop_aliases = {
17
+ 'TimeFrame': {'label': {'frameType', 'label'},
18
+ 'classification': {'classifications', 'classification'}},
19
+ 'BoundingBox': {'label': {'boxType', 'label'},
20
+ 'classification': {'classifications', 'classification'}},
21
+ 'Annotation': {'classification': {'classifications', 'classification'}},
22
+ 'Region': {'classification': {'classifications', 'classification'}},
23
+ 'Interval': {'classification': {'classifications', 'classification'}},
24
+ 'Span': {'classification': {'classifications', 'classification'}},
25
+ 'Chapter': {'classification': {'classifications', 'classification'}},
26
+ 'Polygon': {'classification': {'classifications', 'classification'}},
27
+ 'VideoObject': {'classification': {'classifications', 'classification'}},
28
+ 'Relation': {'classification': {'classifications', 'classification'}},
29
+ }
30
+
31
+ Annotation = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Annotation/v5')
32
+ Region = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Region/v4')
33
+ TimePoint = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/TimePoint/v4')
34
+ Interval = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Interval/v4')
35
+ Span = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Span/v4')
36
+ TimeFrame = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/TimeFrame/v5')
37
+ Chapter = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Chapter/v5')
38
+ Polygon = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Polygon/v4')
39
+ BoundingBox = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/BoundingBox/v4')
40
+ VideoObject = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/VideoObject/v4')
41
+ Relation = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Relation/v4')
42
+ Alignment = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Alignment/v1')
43
+ _typevers = {'Annotation': 'v5', 'Region': 'v4', 'TimePoint': 'v4', 'Interval': 'v4', 'Span': 'v4', 'TimeFrame': 'v5', 'Chapter': 'v5', 'Polygon': 'v4', 'BoundingBox': 'v4', 'VideoObject': 'v4', 'Relation': 'v4', 'Alignment': 'v1'}
@@ -196,7 +196,7 @@ class DocumentTypesBase(ClamsTypesBase):
196
196
  class ThingType(ThingTypesBase):
197
197
  """
198
198
  This class contains the topmost CLAMS thing type
199
- defined in the spec version 1.0.2 as a class variable.
199
+ defined in the spec version 1.0.4 as a class variable.
200
200
  """
201
201
  Thing = ClamsTypesBase('http://mmif.clams.ai/vocabulary/Thing/v1')
202
- typevers = {'Thing': 'v1'}
202
+ _typevers = {'Thing': 'v1'}
@@ -1,4 +1,4 @@
1
- # Spec version 1.0.2
1
+ # Spec version 1.0.4
2
2
  # This file is auto-generated by setup.py
3
3
 
4
4
  from .base_types import DocumentTypesBase
@@ -7,11 +7,11 @@ from .base_types import DocumentTypesBase
7
7
  class DocumentTypes(DocumentTypesBase):
8
8
  """
9
9
  This class contains the CLAMS document types
10
- defined in the spec version 1.0.2 as class variables.
10
+ defined in the spec version 1.0.4 as class variables.
11
11
  """
12
12
  Document = DocumentTypesBase('http://mmif.clams.ai/vocabulary/Document/v1')
13
13
  VideoDocument = DocumentTypesBase('http://mmif.clams.ai/vocabulary/VideoDocument/v1')
14
14
  AudioDocument = DocumentTypesBase('http://mmif.clams.ai/vocabulary/AudioDocument/v1')
15
15
  ImageDocument = DocumentTypesBase('http://mmif.clams.ai/vocabulary/ImageDocument/v1')
16
16
  TextDocument = DocumentTypesBase('http://mmif.clams.ai/vocabulary/TextDocument/v1')
17
- typevers = {'Document': 'v1', 'VideoDocument': 'v1', 'AudioDocument': 'v1', 'ImageDocument': 'v1', 'TextDocument': 'v1'}
17
+ _typevers = {'Document': 'v1', 'VideoDocument': 'v1', 'AudioDocument': 'v1', 'ImageDocument': 'v1', 'TextDocument': 'v1'}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mmif-python
3
- Version: 1.0.10
3
+ Version: 1.0.12
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
@@ -10,6 +10,21 @@ Description: ## MultiMedia Interchange Format
10
10
  [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).
11
11
 
12
12
 
13
+ ## mmif-python
14
+ `mmif-python` is a Python implementation of the MMIF data format.
15
+ `mmif-python` provides various helper classes and functions to handle MMIF JSON in Python,
16
+ including ;
17
+
18
+ 1. de-/serialization of MMIF internal data structures to/from JSON
19
+ 2. validation of MMIF JSON
20
+ 3. handling of CLAMS vocabulary types
21
+ 4. navigation of MMIF object via various "search" methods (e.g. `mmif.get_all_views_contain(vocab_type))`)
22
+
23
+ ## For more ...
24
+ * [Version history and patch notes](https://github.com/clamsproject/mmif-python/blob/main/CHANGELOG.md)
25
+ * [MMIF Python API documentation](https://clamsproject.github.io/mmif-python)
26
+ * [MMIF JSON specification and schema](https://clamsproject.github.io/mmif)
27
+
13
28
  Platform: UNKNOWN
14
29
  Classifier: Development Status :: 2 - Pre-Alpha
15
30
  Classifier: Intended Audience :: Developers
@@ -17,5 +32,6 @@ Classifier: License :: OSI Approved :: Apache Software License
17
32
  Classifier: Programming Language :: Python :: 3 :: Only
18
33
  Requires-Python: >=3.8
19
34
  Description-Content-Type: text/markdown
35
+ Provides-Extra: seq
20
36
  Provides-Extra: cv
21
37
  Provides-Extra: dev
@@ -4,6 +4,7 @@ README.md
4
4
  setup.py
5
5
  ./VERSION
6
6
  ./requirements.cv
7
+ ./requirements.seq
7
8
  ./requirements.txt
8
9
  mmif/__init__.py
9
10
  mmif/res/__init__.py
@@ -16,6 +17,7 @@ mmif/serialize/mmif.py
16
17
  mmif/serialize/model.py
17
18
  mmif/serialize/view.py
18
19
  mmif/utils/__init__.py
20
+ mmif/utils/sequence_helper.py
19
21
  mmif/utils/timeunit_helper.py
20
22
  mmif/utils/video_document_helper.py
21
23
  mmif/ver/__init__.py
@@ -11,3 +11,6 @@ pytest
11
11
  pytest-pep8
12
12
  pytest-cov
13
13
  pytype
14
+
15
+ [seq]
16
+ numpy
@@ -0,0 +1 @@
1
+ numpy
@@ -69,7 +69,7 @@ def generate_vocab_enum(spec_version, clams_types_vers, mod_name) -> str:
69
69
  for type_name, type_ver in clams_types_vers:
70
70
  vocab_url = f'http://mmif.clams.ai/vocabulary/{type_name}/{type_ver}'
71
71
  file_out.write(f" {type_name} = {base_class_name}('{vocab_url}')\n")
72
- file_out.write(f" typevers = {dict(clams_types_vers)}\n")
72
+ file_out.write(f" _typevers = {dict(clams_types_vers)}\n")
73
73
 
74
74
  string_out = file_out.getvalue()
75
75
  file_out.close()
@@ -134,7 +134,7 @@ def generate_vocabulary(spec_version, clams_types_vers):
134
134
  f"from .{mod_name} import {class_name}"
135
135
  for mod_name, classes in types.items()
136
136
  for class_name in classes
137
- ) + '\n\n' + "typevers = {**ThingType.typevers, **AnnotationTypes.typevers, **DocumentTypes.typevers}" + '\n'
137
+ ) + '\n\n' + "_typevers = {**ThingType._typevers, **AnnotationTypes._typevers, **DocumentTypes._typevers}" + '\n'
138
138
  )
139
139
  document_types = []
140
140
  annotation_types = []
@@ -276,6 +276,9 @@ with open('requirements.txt') as requirements:
276
276
  with open('requirements.cv') as requirements:
277
277
  cv_requires = requirements.readlines()
278
278
 
279
+ with open('requirements.seq') as requirements:
280
+ seq_requires = requirements.readlines()
281
+
279
282
  setuptools.setup(
280
283
  name=name,
281
284
  version=version,
@@ -294,6 +297,7 @@ setuptools.setup(
294
297
  },
295
298
  install_requires=requires,
296
299
  extras_require={
300
+ 'seq': seq_requires,
297
301
  'cv': cv_requires,
298
302
  'dev': [
299
303
  'pytest',
@@ -1,3 +0,0 @@
1
- ## MultiMedia Interchange Format
2
- [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).
3
-
@@ -1 +0,0 @@
1
- 1.0.10
@@ -1,2 +0,0 @@
1
- __version__ = "1.0.10"
2
- __specver__ = "1.0.2"
@@ -1,33 +0,0 @@
1
- # Spec version 1.0.2
2
- # This file is auto-generated by setup.py
3
-
4
- from .base_types import AnnotationTypesBase
5
-
6
-
7
- class AnnotationTypes(AnnotationTypesBase):
8
- """
9
- This class contains the CLAMS annotation types
10
- defined in the spec version 1.0.2 as class variables.
11
- """
12
-
13
- # prpoerty aliases, first added in MMIF 1.0.2
14
- # map from type short name to a dictionary of property aliases
15
- # where nested dictionary maps from representative name to its aliases
16
- prop_aliases = {
17
- 'TimeFrame': {'label': {'frameType', 'label'}},
18
- 'BoundingBox': {'label': {'boxType', 'label'}},
19
- }
20
-
21
- Annotation = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Annotation/v3')
22
- Region = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Region/v2')
23
- TimePoint = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/TimePoint/v2')
24
- Interval = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Interval/v2')
25
- Span = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Span/v2')
26
- TimeFrame = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/TimeFrame/v3')
27
- Chapter = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Chapter/v3')
28
- Polygon = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Polygon/v2')
29
- BoundingBox = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/BoundingBox/v2')
30
- VideoObject = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/VideoObject/v2')
31
- Relation = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Relation/v2')
32
- Alignment = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Alignment/v1')
33
- typevers = {'Annotation': 'v3', 'Region': 'v2', 'TimePoint': 'v2', 'Interval': 'v2', 'Span': 'v2', 'TimeFrame': 'v3', 'Chapter': 'v3', 'Polygon': 'v2', 'BoundingBox': 'v2', 'VideoObject': 'v2', 'Relation': 'v2', 'Alignment': 'v1'}
File without changes
File without changes