mmif-python 1.0.8__tar.gz → 1.0.10__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.8/mmif_python.egg-info → mmif-python-1.0.10}/PKG-INFO +1 -1
- mmif-python-1.0.10/VERSION +1 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/res/clams.vocabulary.yaml +61 -5
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/res/mmif.json +5 -2
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/serialize/annotation.py +88 -30
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/serialize/mmif.py +42 -1
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/serialize/view.py +5 -2
- mmif-python-1.0.10/mmif/utils/timeunit_helper.py +84 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/utils/video_document_helper.py +6 -61
- mmif-python-1.0.10/mmif/ver/__init__.py +2 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/vocabulary/annotation_types.py +23 -14
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/vocabulary/base_types.py +2 -2
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/vocabulary/document_types.py +2 -2
- mmif-python-1.0.10/mmif_docloc_http/__init__.py +12 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10/mmif_python.egg-info}/PKG-INFO +1 -1
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif_python.egg-info/SOURCES.txt +2 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif_python.egg-info/requires.txt +0 -1
- mmif-python-1.0.10/mmif_python.egg-info/top_level.txt +2 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/setup.py +14 -11
- mmif-python-1.0.8/VERSION +0 -1
- mmif-python-1.0.8/mmif/ver/__init__.py +0 -2
- mmif-python-1.0.8/mmif_python.egg-info/top_level.txt +0 -1
- {mmif-python-1.0.8 → mmif-python-1.0.10}/LICENSE +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/MANIFEST.in +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/README.md +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/__init__.py +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/res/__init__.py +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/res/do-not-edit.txt +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/serialize/__init__.py +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/serialize/model.py +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/utils/__init__.py +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/ver/do-not-edit.txt +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/vocabulary/__init__.py +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif/vocabulary/do-not-edit.txt +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/mmif_python.egg-info/dependency_links.txt +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/requirements.cv +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/requirements.txt +0 -0
- {mmif-python-1.0.8 → mmif-python-1.0.10}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mmif-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.10
|
|
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.10
|
|
@@ -37,13 +37,57 @@ metadata:
|
|
|
37
37
|
defined either at the metadata level, in which case it has scope over all
|
|
38
38
|
annotations of the same type in a view, or at the instance level, in which
|
|
39
39
|
it has scope over just the single annotation.
|
|
40
|
+
labelset:
|
|
41
|
+
type: List of Strings
|
|
42
|
+
description: >-
|
|
43
|
+
When an annotation object contains results of a classification task,
|
|
44
|
+
this metadata is used to specify the label values used in classification.
|
|
45
|
+
Individual annotations then must have <code>label</code> property that
|
|
46
|
+
is one of the values in this list.
|
|
47
|
+
<br><br>
|
|
48
|
+
[Note] Annotations from a classifier app must have this metadata or <code>
|
|
49
|
+
labelsetUri </code> metadata.
|
|
50
|
+
<br><br>
|
|
51
|
+
[Note] Not all of labels specified
|
|
52
|
+
in the <code>labelset</code> must occur in the output annotations. For
|
|
53
|
+
example, a <code>labelset</code> can contain a <i>catch-all</i> negative
|
|
54
|
+
label, but if the negative label can be not interesting enough to keep in
|
|
55
|
+
the output annotation.
|
|
56
|
+
labelsetUri:
|
|
57
|
+
type: String
|
|
58
|
+
description: >-
|
|
59
|
+
A URI to an externally defined labelset. Since the <code>labelset</code>
|
|
60
|
+
metadata is a list of simple strings, this URI can be used to point to a
|
|
61
|
+
more detailed definition of the labelset. This can be a JSON-LD document
|
|
62
|
+
or a SKOS concept scheme, for example.
|
|
63
|
+
<br><br>
|
|
64
|
+
[Note] Annotations from a classifier app must have this metadata or <code>
|
|
65
|
+
labelset </code> metadata.
|
|
40
66
|
|
|
41
67
|
properties:
|
|
42
68
|
document:
|
|
43
69
|
type: ID
|
|
44
70
|
description: >-
|
|
45
71
|
The identifier of the document that the annotation is over.
|
|
46
|
-
|
|
72
|
+
label:
|
|
73
|
+
type: String
|
|
74
|
+
description: >-
|
|
75
|
+
A label given to this object by a classifier. The value must be a simple
|
|
76
|
+
string value of the label and must be one of the values defined in the
|
|
77
|
+
<code>labelset</code> or <code>labelsetUri</code> annotation metadata.
|
|
78
|
+
<br><br>
|
|
79
|
+
[Note] Annotations from a classifier app must have this property.
|
|
80
|
+
classifications:
|
|
81
|
+
type: Map from String to Number
|
|
82
|
+
description: >-
|
|
83
|
+
A map from label values to their "score" numbers provided by a classifier.
|
|
84
|
+
The score can be probability, similarity, confidence, or any other real
|
|
85
|
+
number that was used to determine the label value.
|
|
86
|
+
<br><br>
|
|
87
|
+
[Optional] on top of the <code>label</code> property. However when this
|
|
88
|
+
property is used, the <code>label</code> property must be one of the keys
|
|
89
|
+
and the keys must match to the values defined in the <code>labelset</code>
|
|
90
|
+
or <code>labelsetUri</code> annotation metadata.
|
|
47
91
|
|
|
48
92
|
---
|
|
49
93
|
|
|
@@ -117,7 +161,8 @@ properties:
|
|
|
117
161
|
name: Span
|
|
118
162
|
parent: Interval
|
|
119
163
|
|
|
120
|
-
similarTo:
|
|
164
|
+
similarTo:
|
|
165
|
+
- http://vocab.lappsgrid.org/Region
|
|
121
166
|
|
|
122
167
|
description: >-
|
|
123
168
|
An annotation over a region in primary text data. A Span may be defined by
|
|
@@ -134,10 +179,17 @@ description: >-
|
|
|
134
179
|
segment used in audio processing, but that term has a different meaning in the
|
|
135
180
|
image and video community.
|
|
136
181
|
|
|
182
|
+
|
|
137
183
|
properties:
|
|
138
184
|
frameType:
|
|
139
185
|
type: String
|
|
140
|
-
description: The type of TimeFrame.
|
|
186
|
+
description: The type of TimeFrame. Possible values include, but are not
|
|
187
|
+
limited to, bars, tones, bars-and-tones, speech, noise, music, slate,
|
|
188
|
+
chyron, lower-third, credits, and other.
|
|
189
|
+
<br><br>
|
|
190
|
+
No longer encouraged to use, instead <code>label</code> property
|
|
191
|
+
should replace this property.
|
|
192
|
+
|
|
141
193
|
|
|
142
194
|
---
|
|
143
195
|
|
|
@@ -186,7 +238,11 @@ description: >-
|
|
|
186
238
|
properties:
|
|
187
239
|
boxType:
|
|
188
240
|
type: String
|
|
189
|
-
description:
|
|
241
|
+
description: >-
|
|
242
|
+
The type of BoundingBox. Mostly used for text boxes where we use the value text.
|
|
243
|
+
<br><br>
|
|
244
|
+
No longer encouraged to use, and instead <code>label</code> property
|
|
245
|
+
should replace this property.
|
|
190
246
|
|
|
191
247
|
---
|
|
192
248
|
|
|
@@ -197,7 +253,7 @@ description: >-
|
|
|
197
253
|
A sequence of Polygons, where each Polygon is associated with a
|
|
198
254
|
TimePoint. So a VideoObject is in effect a sequence of image objects at
|
|
199
255
|
certain time points.
|
|
200
|
-
|
|
256
|
+
|
|
201
257
|
properties:
|
|
202
258
|
polygons:
|
|
203
259
|
type: List of IDs
|
|
@@ -32,7 +32,10 @@
|
|
|
32
32
|
"type": "object",
|
|
33
33
|
"patternProperties": {
|
|
34
34
|
".+": {
|
|
35
|
-
"
|
|
35
|
+
"anyOf": [
|
|
36
|
+
{"type": "string"},
|
|
37
|
+
{"type": "array", "items": { "type": "string" }}
|
|
38
|
+
]
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
},
|
|
@@ -66,7 +69,7 @@
|
|
|
66
69
|
"additionalProperties": false,
|
|
67
70
|
"patternProperties": {
|
|
68
71
|
"^https?:\/\/": {
|
|
69
|
-
"
|
|
72
|
+
"type": "object"
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
},
|
|
@@ -19,16 +19,26 @@ from urllib.parse import urlparse
|
|
|
19
19
|
|
|
20
20
|
from mmif.vocabulary import ThingTypesBase, DocumentTypesBase
|
|
21
21
|
from .model import MmifObject, JSON_PRMTV_TYPES
|
|
22
|
+
from .. import DocumentTypes, AnnotationTypes
|
|
23
|
+
import mmif_docloc_http
|
|
22
24
|
|
|
23
25
|
__all__ = ['Annotation', 'AnnotationProperties', 'Document', 'DocumentProperties', 'Text']
|
|
24
26
|
|
|
25
27
|
T = TypeVar('T')
|
|
28
|
+
LIST_PRMTV = typing.List[JSON_PRMTV_TYPES] # list of values (most cases for annotation props)
|
|
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)
|
|
31
|
+
DICT_LIST_PRMTV = typing.Dict[str, LIST_PRMTV] # dict of list of values (even more complex props)
|
|
26
32
|
|
|
27
|
-
from .. import DocumentTypes, AnnotationTypes
|
|
28
33
|
|
|
34
|
+
# some built-in document location helpers
|
|
29
35
|
discovered_docloc_plugins = {
|
|
30
|
-
|
|
36
|
+
'http': mmif_docloc_http,
|
|
37
|
+
'https': mmif_docloc_http
|
|
31
38
|
}
|
|
39
|
+
discovered_docloc_plugins.update({
|
|
40
|
+
name[len('mmif_docloc_'):]: importlib.import_module(name) for _, name, _ in pkgutil.iter_modules() if re.match(r'mmif[-_]docloc[-_]', name)
|
|
41
|
+
})
|
|
32
42
|
|
|
33
43
|
|
|
34
44
|
class Annotation(MmifObject):
|
|
@@ -56,7 +66,32 @@ class Annotation(MmifObject):
|
|
|
56
66
|
# here is the place to parse formatted IDs and store prefixes in the parent mmif object.
|
|
57
67
|
# (see https://github.com/clamsproject/mmif/issues/64#issuecomment-849241309 for discussion)
|
|
58
68
|
super()._deserialize(input_dict)
|
|
59
|
-
|
|
69
|
+
for k, v in self.properties.items():
|
|
70
|
+
self._add_prop_aliases(k, v)
|
|
71
|
+
|
|
72
|
+
def _add_prop_aliases(self, key_to_add, val_to_add):
|
|
73
|
+
"""
|
|
74
|
+
Method to handle aliases of the same property.
|
|
75
|
+
Annotation property aliases were first introduced in MMIF 1.0.2,
|
|
76
|
+
with addition of general `label` property to all `Annotation`
|
|
77
|
+
subtypes, and effectively deprecated `frameType` and `boxType`
|
|
78
|
+
in `TimeFrame` and `BoundingBox` respectively.
|
|
79
|
+
"""
|
|
80
|
+
prop_aliases = AnnotationTypes.prop_aliases.get(self._type.shortname, {})
|
|
81
|
+
for alias_reprep, alias_group in prop_aliases.items():
|
|
82
|
+
if key_to_add in alias_group:
|
|
83
|
+
for alias in alias_group:
|
|
84
|
+
if alias != key_to_add:
|
|
85
|
+
self._props_ephemeral[alias] = val_to_add
|
|
86
|
+
if alias in self.properties.keys():
|
|
87
|
+
warning_msg = f'Found both "{key_to_add}" and "{alias}" in the properties of "{self.id}" annotation in "{self.parent}" view. '
|
|
88
|
+
if alias == alias_reprep:
|
|
89
|
+
warning_msg += f'However "{key_to_add}" is an alias of "{alias_reprep}".'
|
|
90
|
+
else:
|
|
91
|
+
warning_msg += f'However "{key_to_add}" and "{alias}" are boath aliases of "{alias_reprep}".'
|
|
92
|
+
warning_msg += f'Having two synonyms in the same annotation can cause unexpected behavior. '
|
|
93
|
+
warnings.warn(warning_msg, UserWarning)
|
|
94
|
+
|
|
60
95
|
def is_type(self, at_type: Union[str, ThingTypesBase]) -> bool:
|
|
61
96
|
"""
|
|
62
97
|
Check if the @type of this object matches.
|
|
@@ -94,30 +129,40 @@ class Annotation(MmifObject):
|
|
|
94
129
|
|
|
95
130
|
@staticmethod
|
|
96
131
|
def check_prop_value_is_simple_enough(
|
|
97
|
-
value: Union[JSON_PRMTV_TYPES,
|
|
132
|
+
value: Union[JSON_PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]) -> bool:
|
|
133
|
+
|
|
98
134
|
def json_primitives(x):
|
|
99
135
|
return isinstance(x, typing.get_args(JSON_PRMTV_TYPES))
|
|
136
|
+
|
|
137
|
+
def json_primitives_list(x):
|
|
138
|
+
return isinstance(x, list) and all(map(json_primitives, x))
|
|
139
|
+
|
|
140
|
+
def json_primitives_list_of_list(x):
|
|
141
|
+
return all(map(lambda elem: isinstance(elem, list), x) and map(json_primitives, [subelem for elem in x for subelem in elem]))
|
|
142
|
+
|
|
100
143
|
return json_primitives(value) \
|
|
101
|
-
or (
|
|
102
|
-
or (
|
|
144
|
+
or json_primitives_list(value) \
|
|
145
|
+
or json_primitives_list_of_list(value) \
|
|
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())))
|
|
103
147
|
|
|
104
148
|
def add_property(self, name: str,
|
|
105
|
-
value: Union[JSON_PRMTV_TYPES,
|
|
106
|
-
) -> None:
|
|
149
|
+
value: Union[JSON_PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]) -> None:
|
|
107
150
|
"""
|
|
108
151
|
Adds a property to the annotation's properties.
|
|
109
152
|
:param name: the name of the property
|
|
110
153
|
:param value: the property's desired value
|
|
111
154
|
:return: None
|
|
112
155
|
"""
|
|
113
|
-
if self.check_prop_value_is_simple_enough(value):
|
|
114
|
-
|
|
115
|
-
else:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
156
|
+
# if self.check_prop_value_is_simple_enough(value):
|
|
157
|
+
self.properties[name] = value
|
|
158
|
+
# else:
|
|
159
|
+
# raise ValueError("Property values cannot be a complex object. It must be "
|
|
160
|
+
# "either string, number, boolean, None, a JSON array of them, "
|
|
161
|
+
# "or a JSON object of them keyed by strings."
|
|
162
|
+
# f"(\"{name}\": \"{str(value)}\"")
|
|
163
|
+
self._add_prop_aliases(name, value)
|
|
164
|
+
|
|
165
|
+
def get(self, prop_name: str) -> Union['AnnotationProperties', JSON_PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]:
|
|
121
166
|
"""
|
|
122
167
|
A special getter for Annotation properties. This is to allow for
|
|
123
168
|
directly accessing properties without having to go through the
|
|
@@ -148,8 +193,25 @@ class Annotation(MmifObject):
|
|
|
148
193
|
except KeyError:
|
|
149
194
|
return False
|
|
150
195
|
|
|
196
|
+
def _get_label(self) -> str:
|
|
197
|
+
"""
|
|
198
|
+
Another prototypical method to handle property aliases.
|
|
199
|
+
See :meth:`.Annotation._add_prop_aliases` for more details on
|
|
200
|
+
what property aliases are.
|
|
201
|
+
Not recommended to use this method as `_add_prop_aliases` method
|
|
202
|
+
is preferred.
|
|
203
|
+
"""
|
|
204
|
+
if 'label' in self:
|
|
205
|
+
return str(self.get('label'))
|
|
206
|
+
elif self._type.shortname == 'TimeFrame' and 'frameType' in self:
|
|
207
|
+
return str(self.get('frameType'))
|
|
208
|
+
elif self._type.shortname == 'BoundingBox' and 'boxType' in self:
|
|
209
|
+
return str(self.get('boxType'))
|
|
210
|
+
else:
|
|
211
|
+
raise KeyError("No label found in this annotation.")
|
|
212
|
+
|
|
151
213
|
def is_document(self):
|
|
152
|
-
return isinstance(self.
|
|
214
|
+
return isinstance(self._type, DocumentTypesBase)
|
|
153
215
|
|
|
154
216
|
|
|
155
217
|
class Document(Annotation):
|
|
@@ -180,7 +242,7 @@ class Document(Annotation):
|
|
|
180
242
|
super().__init__(doc_obj)
|
|
181
243
|
|
|
182
244
|
def add_property(self, name: str,
|
|
183
|
-
value: Union[JSON_PRMTV_TYPES,
|
|
245
|
+
value: Union[JSON_PRMTV_TYPES, LIST_PRMTV]
|
|
184
246
|
) -> None:
|
|
185
247
|
"""
|
|
186
248
|
Adds a property to the document's properties.
|
|
@@ -249,30 +311,26 @@ class Document(Annotation):
|
|
|
249
311
|
|
|
250
312
|
@property
|
|
251
313
|
def text_language(self) -> str:
|
|
252
|
-
if self.
|
|
314
|
+
if self._type == DocumentTypes.TextDocument:
|
|
253
315
|
return self.properties.text_language
|
|
254
316
|
else:
|
|
255
317
|
raise ValueError("Only TextDocument can have `text` field.")
|
|
256
318
|
|
|
257
319
|
@text_language.setter
|
|
258
320
|
def text_language(self, lang_code: str) -> None:
|
|
259
|
-
if self.
|
|
321
|
+
if self._type == DocumentTypes.TextDocument:
|
|
260
322
|
self.properties.text_language = lang_code
|
|
261
323
|
else:
|
|
262
324
|
raise ValueError("Only TextDocument can have `text` field.")
|
|
263
325
|
|
|
264
326
|
@property
|
|
265
327
|
def text_value(self) -> str:
|
|
266
|
-
if self.
|
|
328
|
+
if self._type == DocumentTypes.TextDocument:
|
|
267
329
|
if self.location:
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return textvalue
|
|
273
|
-
else:
|
|
274
|
-
# TODO (krim @ 7/11/21): add more handlers for other types of locations (e.g. s3, https, ...)
|
|
275
|
-
return ''
|
|
330
|
+
f = open(self.location_path(nonexist_ok=False), 'r', encoding='utf8')
|
|
331
|
+
textvalue = f.read()
|
|
332
|
+
f.close()
|
|
333
|
+
return textvalue
|
|
276
334
|
else:
|
|
277
335
|
return self.properties.text_value
|
|
278
336
|
else:
|
|
@@ -280,7 +338,7 @@ class Document(Annotation):
|
|
|
280
338
|
|
|
281
339
|
@text_value.setter
|
|
282
340
|
def text_value(self, text_value: str) -> None:
|
|
283
|
-
if self.
|
|
341
|
+
if self._type == DocumentTypes.TextDocument:
|
|
284
342
|
self.properties.text_value = text_value
|
|
285
343
|
else:
|
|
286
344
|
raise ValueError("Only TextDocument can have `text` field.")
|
|
@@ -12,10 +12,10 @@ from datetime import datetime
|
|
|
12
12
|
from typing import List, Union, Optional, Dict, ClassVar, cast
|
|
13
13
|
|
|
14
14
|
import jsonschema.validators
|
|
15
|
-
|
|
16
15
|
import mmif
|
|
17
16
|
from mmif import ThingTypesBase
|
|
18
17
|
from mmif.vocabulary import AnnotationTypes, DocumentTypes
|
|
18
|
+
|
|
19
19
|
from .annotation import Annotation, Document
|
|
20
20
|
from .model import MmifObject, DataList
|
|
21
21
|
from .view import View
|
|
@@ -478,6 +478,47 @@ class Mmif(MmifObject):
|
|
|
478
478
|
if at_types in view.metadata.contains:
|
|
479
479
|
return view
|
|
480
480
|
return None
|
|
481
|
+
|
|
482
|
+
def _get_linear_anchor_point(self, ann: Annotation, start: bool = True) -> Union[int, float]:
|
|
483
|
+
# 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
|
+
"""
|
|
485
|
+
Retrieves the anchor point of the annotation. Currently, this method only supports linear anchors,
|
|
486
|
+
namely time and text, hence does not work with spatial anchors (polygons or video-object).
|
|
487
|
+
|
|
488
|
+
:param ann: An Annotation object that has a linear anchor point. Namely, some subtypes of `Region` vocabulary type.
|
|
489
|
+
:param start: If True, returns the start anchor point. Otherwise, returns the end anchor point. N/A for `timePoint` anchors.
|
|
490
|
+
:return: the anchor point of the annotation. 1d for linear regions (time, text)
|
|
491
|
+
"""
|
|
492
|
+
props = ann.properties
|
|
493
|
+
if 'timePoint' in props:
|
|
494
|
+
return ann.get_property('timePoint')
|
|
495
|
+
elif 'targets' in props:
|
|
496
|
+
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)))
|
|
505
|
+
return self._get_linear_anchor_point(target, start=start)
|
|
506
|
+
elif (start and 'start' in props) or (not start and 'end' in props):
|
|
507
|
+
return ann.get_property('start' if start else 'end')
|
|
508
|
+
else:
|
|
509
|
+
raise ValueError(f"{ann.id} ({ann.at_type}) does not have a valid anchor point. Is it a valid 'Region' type?")
|
|
510
|
+
|
|
511
|
+
def get_start(self, annotation: Annotation) -> Union[int, float]:
|
|
512
|
+
"""
|
|
513
|
+
An alias to `get_anchor_point` method with `start=True`.
|
|
514
|
+
"""
|
|
515
|
+
return self._get_linear_anchor_point(annotation, start=True)
|
|
516
|
+
|
|
517
|
+
def get_end(self, annotation: Annotation) -> Union[int, float]:
|
|
518
|
+
"""
|
|
519
|
+
An alias to `get_anchor_point` method with `start=False`.
|
|
520
|
+
"""
|
|
521
|
+
return self._get_linear_anchor_point(annotation, start=False)
|
|
481
522
|
|
|
482
523
|
# pytype: disable=bad-return-type
|
|
483
524
|
def __getitem__(self, item: str) -> Union[Document, View, Annotation]:
|
|
@@ -186,7 +186,7 @@ class View(MmifObject):
|
|
|
186
186
|
properties.items())):
|
|
187
187
|
yield annotation
|
|
188
188
|
|
|
189
|
-
def get_annotation_by_id(self, ann_id):
|
|
189
|
+
def get_annotation_by_id(self, ann_id) -> Annotation:
|
|
190
190
|
ann_found = self.annotations.get(ann_id)
|
|
191
191
|
if ann_found is None or not isinstance(ann_found, Annotation):
|
|
192
192
|
raise KeyError(f"Annotation \"{ann_id}\" is not found in view {self.id}.")
|
|
@@ -367,7 +367,10 @@ class AnnotationsList(DataList[Union[Annotation, Document]]):
|
|
|
367
367
|
class ContainsDict(DataDict[ThingTypesBase, Contain]):
|
|
368
368
|
|
|
369
369
|
def _deserialize(self, input_dict: dict) -> None:
|
|
370
|
-
|
|
370
|
+
for key, value in input_dict.items():
|
|
371
|
+
if isinstance(key, str):
|
|
372
|
+
key = ThingTypesBase.from_str(key)
|
|
373
|
+
self._items[key] = Contain(value)
|
|
371
374
|
|
|
372
375
|
def update(self, other: Union[dict, 'ContainsDict'], overwrite=False):
|
|
373
376
|
for k, v in other.items():
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
UNIT_NORMALIZATION = {
|
|
6
|
+
'm': 'millisecond',
|
|
7
|
+
'ms': 'millisecond',
|
|
8
|
+
'msec': 'millisecond',
|
|
9
|
+
'millisecond': 'millisecond',
|
|
10
|
+
'milliseconds': 'millisecond',
|
|
11
|
+
's': 'second',
|
|
12
|
+
'se': 'second',
|
|
13
|
+
'sec': 'second',
|
|
14
|
+
'second': 'second',
|
|
15
|
+
'seconds': 'second',
|
|
16
|
+
'f': 'frame',
|
|
17
|
+
'fr': 'frame',
|
|
18
|
+
'frame': 'frame',
|
|
19
|
+
'frames': 'frame',
|
|
20
|
+
'i': 'isoformat',
|
|
21
|
+
'iso': 'isoformat',
|
|
22
|
+
'isoformat': 'isoformat',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _isoformat_to_millisecond(isoformat: str) -> int:
|
|
27
|
+
t = datetime.strptime(isoformat, '%H:%M:%S.%f')
|
|
28
|
+
return int(1000 * (t.hour * 3600 + t.minute * 60 + t.second + t.microsecond / 1000000))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _millisecond_to_isoformat(millisecond: int) -> str:
|
|
32
|
+
t = datetime.utcfromtimestamp(millisecond / 1000)
|
|
33
|
+
return t.strftime('%H:%M:%S.%f')[:-3]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _second_to_isoformat(second: float) -> str:
|
|
37
|
+
t = datetime.utcfromtimestamp(second)
|
|
38
|
+
return t.strftime('%H:%M:%S.%f')[:-3] # python strftime will return "microsecond" with 6 digits
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def convert(t: Union[int, float, str], in_unit: str, out_unit: str, fps: float) -> Union[int, float, str]:
|
|
42
|
+
"""
|
|
43
|
+
Converts time from one unit to another. Works with ``frames``, ``seconds``, ``milliseconds``.
|
|
44
|
+
|
|
45
|
+
:param t: time value to convert
|
|
46
|
+
:param in_unit: input time unit, one of ``frames``, ``seconds``, ``milliseconds``
|
|
47
|
+
:param out_unit: output time unit, one of ``frames``, ``seconds``, ``milliseconds``
|
|
48
|
+
:param fps: frames per second
|
|
49
|
+
:return: converted time value
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
in_unit = UNIT_NORMALIZATION[in_unit]
|
|
53
|
+
except KeyError:
|
|
54
|
+
raise ValueError(f"Not supported time unit: {in_unit}")
|
|
55
|
+
try:
|
|
56
|
+
out_unit = UNIT_NORMALIZATION[out_unit]
|
|
57
|
+
except KeyError:
|
|
58
|
+
raise ValueError(f"Not supported time unit: {out_unit}")
|
|
59
|
+
if in_unit == 'isoformat':
|
|
60
|
+
if isinstance(t, str):
|
|
61
|
+
t = _isoformat_to_millisecond(t)
|
|
62
|
+
in_unit = 'millisecond'
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError(f"Invalid time format: ISO format string expected, but got {t} of type {type(t)}")
|
|
65
|
+
# s>s, ms>ms, f>f
|
|
66
|
+
if in_unit == out_unit:
|
|
67
|
+
return t
|
|
68
|
+
elif out_unit == 'frame':
|
|
69
|
+
# ms>f
|
|
70
|
+
if 'millisecond' == in_unit:
|
|
71
|
+
return int(t / 1000 * fps)
|
|
72
|
+
# s>f
|
|
73
|
+
elif 'second' == in_unit:
|
|
74
|
+
return int(t * fps)
|
|
75
|
+
# s>(ms or i)
|
|
76
|
+
elif in_unit == 'second':
|
|
77
|
+
return t * 1000 if out_unit == 'millisecond' else _second_to_isoformat(t)
|
|
78
|
+
# ms>(s or i)
|
|
79
|
+
elif in_unit == 'millisecond':
|
|
80
|
+
return t / 1000 if out_unit == 'second' else _millisecond_to_isoformat(t)
|
|
81
|
+
# f>ms, f>s
|
|
82
|
+
else:
|
|
83
|
+
return (t / fps) if out_unit == 'second' else (t / fps * 1000) # pytype: disable=bad-return-type
|
|
84
|
+
|
|
@@ -4,6 +4,7 @@ from typing import List, Union, Tuple
|
|
|
4
4
|
|
|
5
5
|
import mmif
|
|
6
6
|
from mmif import Annotation, Document, Mmif
|
|
7
|
+
from mmif.utils.timeunit_helper import convert
|
|
7
8
|
from mmif.vocabulary import DocumentTypes
|
|
8
9
|
|
|
9
10
|
for cv_dep in ('cv2', 'ffmpeg', 'PIL'):
|
|
@@ -19,23 +20,6 @@ FPS_DOCPROP_KEY = 'fps'
|
|
|
19
20
|
FRAMECOUNT_DOCPROP_KEY = 'frameCount'
|
|
20
21
|
DURATION_DOCPROP_KEY = 'duration'
|
|
21
22
|
DURATIONUNIT_DOCPROP_KEY = 'durationTimeUnit'
|
|
22
|
-
|
|
23
|
-
UNIT_NORMALIZATION = {
|
|
24
|
-
'm': 'millisecond',
|
|
25
|
-
'ms': 'millisecond',
|
|
26
|
-
'msec': 'millisecond',
|
|
27
|
-
'millisecond': 'millisecond',
|
|
28
|
-
'milliseconds': 'millisecond',
|
|
29
|
-
's': 'second',
|
|
30
|
-
'se': 'second',
|
|
31
|
-
'sec': 'second',
|
|
32
|
-
'second': 'second',
|
|
33
|
-
'seconds': 'second',
|
|
34
|
-
'f': 'frame',
|
|
35
|
-
'fr': 'frame',
|
|
36
|
-
'frame': 'frame',
|
|
37
|
-
'frames': 'frame',
|
|
38
|
-
}
|
|
39
23
|
|
|
40
24
|
|
|
41
25
|
def capture(video_document: Document):
|
|
@@ -120,7 +104,7 @@ def get_mid_framenum(mmif: Mmif, time_frame: Annotation):
|
|
|
120
104
|
timeunit = time_frame.get_property('timeUnit')
|
|
121
105
|
video_document = mmif[time_frame.get_property('document')]
|
|
122
106
|
fps = get_framerate(video_document)
|
|
123
|
-
return
|
|
107
|
+
return convert(mmif.get_start(time_frame) + mmif.get_end(time_frame), timeunit, 'frame', fps) // 2
|
|
124
108
|
|
|
125
109
|
|
|
126
110
|
def extract_mid_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
|
|
@@ -155,45 +139,6 @@ def sample_frames(start_frame: int, end_frame: int, sample_ratio: int = 1) -> Li
|
|
|
155
139
|
return frame_nums
|
|
156
140
|
|
|
157
141
|
|
|
158
|
-
def convert(time: Union[int, float], in_unit: str, out_unit: str, fps: float) -> Union[int, float]:
|
|
159
|
-
"""
|
|
160
|
-
Converts time from one unit to another. Works with ``frames``, ``seconds``, ``milliseconds``.
|
|
161
|
-
|
|
162
|
-
:param time: time value to convert
|
|
163
|
-
:param in_unit: input time unit, one of ``frames``, ``seconds``, ``milliseconds``
|
|
164
|
-
:param out_unit: output time unit, one of ``frames``, ``seconds``, ``milliseconds``
|
|
165
|
-
:param fps: frames per second
|
|
166
|
-
:return: converted time value
|
|
167
|
-
"""
|
|
168
|
-
try:
|
|
169
|
-
in_unit = UNIT_NORMALIZATION[in_unit]
|
|
170
|
-
except KeyError:
|
|
171
|
-
raise ValueError(f"Not supported time unit: {in_unit}")
|
|
172
|
-
try:
|
|
173
|
-
out_unit = UNIT_NORMALIZATION[out_unit]
|
|
174
|
-
except KeyError:
|
|
175
|
-
raise ValueError(f"Not supported time unit: {out_unit}")
|
|
176
|
-
# s>s, ms>ms, f>f
|
|
177
|
-
if in_unit == out_unit:
|
|
178
|
-
return time
|
|
179
|
-
elif out_unit == 'frame':
|
|
180
|
-
# ms>f
|
|
181
|
-
if 'millisecond' == in_unit:
|
|
182
|
-
return int(time / 1000 * fps)
|
|
183
|
-
# s>f
|
|
184
|
-
elif 'second' == in_unit:
|
|
185
|
-
return int(time * fps)
|
|
186
|
-
# s>ms
|
|
187
|
-
elif in_unit == 'second':
|
|
188
|
-
return time * 1000
|
|
189
|
-
# ms>s
|
|
190
|
-
elif in_unit == 'millisecond':
|
|
191
|
-
return time // 1000
|
|
192
|
-
# f>ms, f>s
|
|
193
|
-
else:
|
|
194
|
-
return (time / fps) if out_unit == 'second' else (time / fps * 1000) # pytype: disable=bad-return-type
|
|
195
|
-
|
|
196
|
-
|
|
197
142
|
def get_annotation_property(mmif, annotation, prop_name):
|
|
198
143
|
"""
|
|
199
144
|
.. deprecated:: 1.0.8
|
|
@@ -208,7 +153,7 @@ def get_annotation_property(mmif, annotation, prop_name):
|
|
|
208
153
|
return annotation.get_property(prop_name)
|
|
209
154
|
|
|
210
155
|
|
|
211
|
-
def convert_timepoint(mmif: Mmif, timepoint: Annotation, out_unit: str) -> Union[int, float]:
|
|
156
|
+
def convert_timepoint(mmif: Mmif, timepoint: Annotation, out_unit: str) -> Union[int, float, str]:
|
|
212
157
|
"""
|
|
213
158
|
Converts a time point included in an annotation to a different time unit.
|
|
214
159
|
The input annotation must have ``timePoint`` property.
|
|
@@ -223,7 +168,7 @@ def convert_timepoint(mmif: Mmif, timepoint: Annotation, out_unit: str) -> Union
|
|
|
223
168
|
return convert(timepoint.get_property('timePoint'), in_unit, out_unit, get_framerate(vd))
|
|
224
169
|
|
|
225
170
|
|
|
226
|
-
def convert_timeframe(mmif: Mmif, time_frame: Annotation, out_unit: str) -> Union[Tuple[int,
|
|
171
|
+
def convert_timeframe(mmif: Mmif, time_frame: Annotation, out_unit: str) -> Union[Tuple[Union[int, float, str], Union[int, float, str]]]:
|
|
227
172
|
"""
|
|
228
173
|
Converts start and end points in a ``TimeFrame`` annotation a different time unit.
|
|
229
174
|
|
|
@@ -234,8 +179,8 @@ def convert_timeframe(mmif: Mmif, time_frame: Annotation, out_unit: str) -> Unio
|
|
|
234
179
|
"""
|
|
235
180
|
in_unit = time_frame.get_property('timeUnit')
|
|
236
181
|
vd = mmif[time_frame.get_property('document')]
|
|
237
|
-
return convert(
|
|
238
|
-
convert(
|
|
182
|
+
return convert(mmif.get_start(time_frame), in_unit, out_unit, get_framerate(vd)), \
|
|
183
|
+
convert(mmif.get_end(time_frame), in_unit, out_unit, get_framerate(vd))
|
|
239
184
|
|
|
240
185
|
|
|
241
186
|
def framenum_to_second(video_doc: Document, frame: int):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Spec version 1.0.
|
|
1
|
+
# Spec version 1.0.2
|
|
2
2
|
# This file is auto-generated by setup.py
|
|
3
3
|
|
|
4
4
|
from .base_types import AnnotationTypesBase
|
|
@@ -7,18 +7,27 @@ from .base_types import AnnotationTypesBase
|
|
|
7
7
|
class AnnotationTypes(AnnotationTypesBase):
|
|
8
8
|
"""
|
|
9
9
|
This class contains the CLAMS annotation types
|
|
10
|
-
defined in the spec version 1.0.
|
|
10
|
+
defined in the spec version 1.0.2 as class variables.
|
|
11
11
|
"""
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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')
|
|
23
32
|
Alignment = AnnotationTypesBase('http://mmif.clams.ai/vocabulary/Alignment/v1')
|
|
24
|
-
typevers = {'Annotation': '
|
|
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'}
|
|
@@ -137,7 +137,7 @@ class ClamsTypesBase(TypesBase):
|
|
|
137
137
|
return True
|
|
138
138
|
|
|
139
139
|
def __hash__(self):
|
|
140
|
-
return hash(
|
|
140
|
+
return hash(f'{self.base_uri}::{self.shortname}')
|
|
141
141
|
|
|
142
142
|
def normalize_attype_vers(self):
|
|
143
143
|
if '.' in self.version and self._check_legacy_version(self.version):
|
|
@@ -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.
|
|
199
|
+
defined in the spec version 1.0.2 as a class variable.
|
|
200
200
|
"""
|
|
201
201
|
Thing = ClamsTypesBase('http://mmif.clams.ai/vocabulary/Thing/v1')
|
|
202
202
|
typevers = {'Thing': 'v1'}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Spec version 1.0.
|
|
1
|
+
# Spec version 1.0.2
|
|
2
2
|
# This file is auto-generated by setup.py
|
|
3
3
|
|
|
4
4
|
from .base_types import DocumentTypesBase
|
|
@@ -7,7 +7,7 @@ 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.
|
|
10
|
+
defined in the spec version 1.0.2 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')
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import urllib.request
|
|
2
|
+
import urllib.error
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def resolve(docloc):
|
|
6
|
+
try:
|
|
7
|
+
if docloc.startswith('http://') or docloc.startswith('https://'):
|
|
8
|
+
return urllib.request.urlretrieve(docloc)[0]
|
|
9
|
+
else:
|
|
10
|
+
raise ValueError(f'cannot handle document location scheme: {docloc}')
|
|
11
|
+
except urllib.error.URLError as e:
|
|
12
|
+
raise e
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mmif-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.10
|
|
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
|
|
@@ -16,6 +16,7 @@ mmif/serialize/mmif.py
|
|
|
16
16
|
mmif/serialize/model.py
|
|
17
17
|
mmif/serialize/view.py
|
|
18
18
|
mmif/utils/__init__.py
|
|
19
|
+
mmif/utils/timeunit_helper.py
|
|
19
20
|
mmif/utils/video_document_helper.py
|
|
20
21
|
mmif/ver/__init__.py
|
|
21
22
|
mmif/ver/do-not-edit.txt
|
|
@@ -24,6 +25,7 @@ mmif/vocabulary/annotation_types.py
|
|
|
24
25
|
mmif/vocabulary/base_types.py
|
|
25
26
|
mmif/vocabulary/do-not-edit.txt
|
|
26
27
|
mmif/vocabulary/document_types.py
|
|
28
|
+
mmif_docloc_http/__init__.py
|
|
27
29
|
mmif_python.egg-info/PKG-INFO
|
|
28
30
|
mmif_python.egg-info/SOURCES.txt
|
|
29
31
|
mmif_python.egg-info/dependency_links.txt
|
|
@@ -162,15 +162,19 @@ def generate_vocabulary(spec_version, clams_types_vers):
|
|
|
162
162
|
|
|
163
163
|
|
|
164
164
|
def get_latest_mmif_gittag():
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
165
|
+
if LOCALMMIF is not None:
|
|
166
|
+
tags = subprocess.run(f'git --git-dir {LOCALMMIF}/.git --no-pager tag'.split(),
|
|
167
|
+
capture_output=True).stdout.decode('utf-8').split('\n')
|
|
168
|
+
else:
|
|
169
|
+
cur_p = 1
|
|
170
|
+
body = [None]
|
|
171
|
+
tags = []
|
|
172
|
+
while len(body) > 0:
|
|
173
|
+
# for when we have more than 30 (default pagination size) tags
|
|
174
|
+
res = request.urlopen(f'https://api.github.com/repos/clamsproject/mmif/tags?per_page=100&page={cur_p}')
|
|
175
|
+
body = json.loads(res.read())
|
|
176
|
+
tags.extend([tag['name'] for tag in body])
|
|
177
|
+
cur_p += 1
|
|
174
178
|
# sort and return highest version
|
|
175
179
|
print(tags)
|
|
176
180
|
mmif_ver_format = lambda x: re.match(r'\d+\.\d+\.\d$', x)
|
|
@@ -209,7 +213,7 @@ def prep_ext_files(setuptools_cmd):
|
|
|
209
213
|
latest_mmif_gittag = get_latest_mmif_gittag()
|
|
210
214
|
spec_file_gitref = latest_mmif_gittag if '.dev' not in version else 'develop'
|
|
211
215
|
# legacy version tags were formatted as xx-a.b.c (e.g., vocab-0.0.1)
|
|
212
|
-
spec_version = latest_mmif_gittag.split('-')[-1]
|
|
216
|
+
spec_version = latest_mmif_gittag.split('-')[-1].strip()
|
|
213
217
|
# making resources into a python package so that `pkg_resources` can access resource files
|
|
214
218
|
res_dir = generate_subpack(mmif_name, mmif_res_pkg)
|
|
215
219
|
|
|
@@ -296,7 +300,6 @@ setuptools.setup(
|
|
|
296
300
|
'pytest-pep8',
|
|
297
301
|
'pytest-cov',
|
|
298
302
|
'pytype',
|
|
299
|
-
'sphinx'
|
|
300
303
|
]
|
|
301
304
|
},
|
|
302
305
|
python_requires='>=3.8',
|
mmif-python-1.0.8/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.0.8
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
mmif
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|