mmif-python 1.1.2__tar.gz → 1.2.1__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.1.2/mmif_python.egg-info → mmif_python-1.2.1}/PKG-INFO +1 -2
- mmif_python-1.2.1/VERSION +1 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/serialize/annotation.py +101 -43
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/serialize/mmif.py +122 -58
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/serialize/model.py +192 -31
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/serialize/view.py +92 -19
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/utils/cli/__init__.py +1 -0
- mmif_python-1.2.1/mmif/utils/cli/describe.py +140 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/utils/cli/rewind.py +21 -28
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/utils/cli/source.py +5 -10
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/utils/sequence_helper.py +5 -5
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/utils/text_document_helper.py +9 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/utils/video_document_helper.py +11 -6
- mmif_python-1.2.1/mmif/utils/workflow_helper.py +466 -0
- mmif_python-1.2.1/mmif/ver/__init__.py +2 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/vocabulary/base_types.py +11 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1/mmif_python.egg-info}/PKG-INFO +1 -2
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif_python.egg-info/SOURCES.txt +2 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif_python.egg-info/requires.txt +0 -1
- {mmif_python-1.1.2 → mmif_python-1.2.1}/requirements.txt +1 -1
- {mmif_python-1.1.2 → mmif_python-1.2.1}/tests/test_serialize.py +261 -34
- {mmif_python-1.1.2 → mmif_python-1.2.1}/tests/test_utils.py +63 -0
- mmif_python-1.2.1/tests/test_utils_cli.py +306 -0
- mmif_python-1.1.2/VERSION +0 -1
- mmif_python-1.1.2/mmif/ver/__init__.py +0 -2
- mmif_python-1.1.2/tests/test_utils_cli.py +0 -170
- {mmif_python-1.1.2 → mmif_python-1.2.1}/LICENSE +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/MANIFEST.in +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/README.md +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/__init__.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/res/__init__.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/res/clams.vocabulary.yaml +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/res/do-not-edit.txt +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/res/mmif.json +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/serialize/__init__.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/utils/__init__.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/utils/timeunit_helper.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/ver/do-not-edit.txt +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/vocabulary/__init__.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/vocabulary/annotation_types.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/vocabulary/do-not-edit.txt +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif/vocabulary/document_types.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif_docloc_http/__init__.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif_python.egg-info/dependency_links.txt +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif_python.egg-info/entry_points.txt +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/mmif_python.egg-info/top_level.txt +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/requirements.cv +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/requirements.seq +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/setup.cfg +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/setup.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/tests/test_versioncompat.py +0 -0
- {mmif_python-1.1.2 → mmif_python-1.2.1}/tests/test_vocab.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mmif-python
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
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
|
|
@@ -11,7 +11,6 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist: deepdiff>5
|
|
15
14
|
Requires-Dist: orderly-set==5.3.*
|
|
16
15
|
Requires-Dist: jsonschema
|
|
17
16
|
Provides-Extra: seq
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.2.1
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
The :mod:`annotation` module contains the classes used to represent a
|
|
3
3
|
MMIF annotation as a live Python object.
|
|
4
4
|
|
|
5
|
-
In MMIF, annotations are created by apps in a
|
|
5
|
+
In MMIF, annotations are created by apps in a workflow as a part
|
|
6
6
|
of a view. For documentation on how views are represented, see
|
|
7
7
|
:mod:`mmif.serialize.view`.
|
|
8
8
|
"""
|
|
@@ -74,10 +74,11 @@ class Annotation(MmifObject):
|
|
|
74
74
|
|
|
75
75
|
def _cache_alignment(self, alignment_ann: 'Annotation', alignedto_ann: 'Annotation') -> None:
|
|
76
76
|
"""
|
|
77
|
-
Cache alignment information. This cache will not be serialized.
|
|
78
|
-
|
|
77
|
+
Cache alignment information. This cache will not be serialized.
|
|
78
|
+
|
|
79
79
|
:param alignment_ann: the Alignment annotation that has this annotation on one side
|
|
80
80
|
:param alignedto_ann: the annotation that this annotation is aligned to (other side of Alignment)
|
|
81
|
+
:return: None
|
|
81
82
|
"""
|
|
82
83
|
self._alignments[alignment_ann] = alignedto_ann
|
|
83
84
|
|
|
@@ -228,7 +229,7 @@ class Annotation(MmifObject):
|
|
|
228
229
|
value: Union[PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]) -> None:
|
|
229
230
|
"""
|
|
230
231
|
Adds a property to the annotation's properties.
|
|
231
|
-
|
|
232
|
+
|
|
232
233
|
:param name: the name of the property
|
|
233
234
|
:param value: the property's desired value
|
|
234
235
|
:return: None
|
|
@@ -256,18 +257,41 @@ class Annotation(MmifObject):
|
|
|
256
257
|
|
|
257
258
|
def get(self, prop_name: str, default=None):
|
|
258
259
|
"""
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
260
|
+
Safe property access with optional default value.
|
|
261
|
+
|
|
262
|
+
Searches for an annotation property by name and returns its value,
|
|
263
|
+
or a default value if not found. This method searches in multiple
|
|
264
|
+
locations with the following priority:
|
|
265
|
+
|
|
266
|
+
1. Direct properties (in ``annotation.properties``)
|
|
267
|
+
2. Ephemeral properties (view-level metadata from ``contains``)
|
|
268
|
+
3. Special fields (``@type``, ``properties``)
|
|
269
|
+
|
|
270
|
+
This allows convenient access to properties without explicitly
|
|
271
|
+
checking the ``properties`` object or view-level metadata.
|
|
272
|
+
|
|
273
|
+
:param prop_name: The name of the property to retrieve
|
|
274
|
+
:param default: The value to return if the property is not found (default: None)
|
|
275
|
+
:return: The property value, or the default value if not found
|
|
276
|
+
|
|
277
|
+
Examples
|
|
278
|
+
--------
|
|
279
|
+
.. code-block:: python
|
|
280
|
+
|
|
281
|
+
# Access annotation properties:
|
|
282
|
+
label = annotation.get('label', default='unknown')
|
|
283
|
+
start_time = annotation.get('start', default=0)
|
|
284
|
+
|
|
285
|
+
# Access @type:
|
|
286
|
+
at_type = annotation.get('@type')
|
|
287
|
+
|
|
288
|
+
# Safe access with custom default:
|
|
289
|
+
targets = annotation.get('targets', default=[])
|
|
290
|
+
|
|
291
|
+
See Also
|
|
292
|
+
--------
|
|
293
|
+
__getitem__ : Direct property access that raises KeyError when not found
|
|
294
|
+
get_property : Alias for this method
|
|
271
295
|
"""
|
|
272
296
|
try:
|
|
273
297
|
return self.__getitem__(prop_name)
|
|
@@ -336,29 +360,32 @@ class Document(Annotation):
|
|
|
336
360
|
) -> None:
|
|
337
361
|
"""
|
|
338
362
|
Adds a property to the document's properties.
|
|
339
|
-
|
|
340
|
-
Unlike the parent :class:`Annotation` class, added properties of a
|
|
341
|
-
``Document`` object can be lost during serialization unless it belongs
|
|
342
|
-
to somewhere in a ``Mmif`` object. This is because we want to keep
|
|
343
|
-
``Document`` object as "read-only" as possible. Thus, if you want to add
|
|
344
|
-
a property to a ``Document`` object,
|
|
345
|
-
|
|
346
|
-
* add the document to a ``Mmif`` object (either in the documents list or
|
|
363
|
+
|
|
364
|
+
Unlike the parent :class:`Annotation` class, added properties of a
|
|
365
|
+
``Document`` object can be lost during serialization unless it belongs
|
|
366
|
+
to somewhere in a ``Mmif`` object. This is because we want to keep
|
|
367
|
+
``Document`` object as "read-only" as possible. Thus, if you want to add
|
|
368
|
+
a property to a ``Document`` object,
|
|
369
|
+
|
|
370
|
+
* add the document to a ``Mmif`` object (either in the documents list or
|
|
347
371
|
in a view from the views list), or
|
|
348
372
|
* directly write to ``Document.properties`` instead of using this method
|
|
349
|
-
(which is not recommended).
|
|
350
|
-
|
|
351
|
-
With the former method, the SDK will record the added property as a
|
|
352
|
-
`Annotation` annotation object, separate from the original `Document`
|
|
373
|
+
(which is not recommended).
|
|
374
|
+
|
|
375
|
+
With the former method, the SDK will record the added property as a
|
|
376
|
+
`Annotation` annotation object, separate from the original `Document`
|
|
353
377
|
object. See :meth:`.Mmif.generate_capital_annotations()` for more.
|
|
354
|
-
|
|
378
|
+
|
|
355
379
|
A few notes to keep in mind:
|
|
356
|
-
|
|
357
|
-
#. You can't overwrite an existing property of a ``Document`` object.
|
|
358
|
-
#. A MMIF can have multiple ``Annotation`` objects with the same
|
|
380
|
+
|
|
381
|
+
#. You can't overwrite an existing property of a ``Document`` object.
|
|
382
|
+
#. A MMIF can have multiple ``Annotation`` objects with the same
|
|
359
383
|
property name but different values. When this happens, the SDK will
|
|
360
|
-
only keep the latest value (in order of appearances in views list) of
|
|
384
|
+
only keep the latest value (in order of appearances in views list) of
|
|
361
385
|
the property, effectively overwriting the previous values.
|
|
386
|
+
|
|
387
|
+
:param name: the name of the property
|
|
388
|
+
:param value: the property's desired value (note: Document accepts fewer value types than Annotation)
|
|
362
389
|
"""
|
|
363
390
|
# we don't checking if this k-v already exists in _original (new props) or _ephemeral (read from existing MMIF)
|
|
364
391
|
# because it is impossible to keep the _original updated when a new annotation is added (via `new_annotation`)
|
|
@@ -378,13 +405,44 @@ class Document(Annotation):
|
|
|
378
405
|
|
|
379
406
|
def get(self, prop_name, default=None):
|
|
380
407
|
"""
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
408
|
+
Safe property access with optional default value for Document objects.
|
|
409
|
+
|
|
410
|
+
Searches for a document property by name and returns its value, or a
|
|
411
|
+
default value if not found. Documents have a more complex property
|
|
412
|
+
hierarchy than regular annotations:
|
|
413
|
+
|
|
414
|
+
Priority order (highest to lowest):
|
|
415
|
+
1. Special fields ('id', 'location')
|
|
416
|
+
2. Pending properties (added after creation, to be serialized as ``Annotation`` objects)
|
|
417
|
+
3. Ephemeral properties (from existing ``Annotation`` annotations or view metadata)
|
|
418
|
+
4. Original properties (in ``document.properties``)
|
|
419
|
+
|
|
420
|
+
This allows convenient access to all document properties regardless of
|
|
421
|
+
where they're stored internally.
|
|
422
|
+
|
|
423
|
+
:param prop_name: The name of the property to retrieve
|
|
424
|
+
:param default: The value to return if the property is not found (default: None)
|
|
425
|
+
:return: The property value, or the default value if not found
|
|
426
|
+
|
|
427
|
+
Examples
|
|
428
|
+
--------
|
|
429
|
+
.. code-block:: python
|
|
430
|
+
|
|
431
|
+
# Access document properties:
|
|
432
|
+
mime = document.get('mime', default='application/octet-stream')
|
|
433
|
+
location = document.get('location')
|
|
434
|
+
|
|
435
|
+
# Access properties added after creation (pending):
|
|
436
|
+
author = document.get('author', default='anonymous')
|
|
437
|
+
publisher = document.get('publisher')
|
|
438
|
+
|
|
439
|
+
# Access ephemeral properties from Annotation objects:
|
|
440
|
+
sentiment = document.get('sentiment', default='neutral')
|
|
441
|
+
|
|
442
|
+
See Also
|
|
443
|
+
--------
|
|
444
|
+
add_property : Add a new property to the document
|
|
445
|
+
Mmif.generate_capital_annotations : How pending properties are serialized
|
|
388
446
|
"""
|
|
389
447
|
if prop_name == 'id':
|
|
390
448
|
# because all three dicts have `id` key as required field, we need
|
|
@@ -399,7 +457,7 @@ class Document(Annotation):
|
|
|
399
457
|
elif prop_name in self._props_ephemeral:
|
|
400
458
|
return self._props_ephemeral[prop_name]
|
|
401
459
|
else:
|
|
402
|
-
return super().get(prop_name)
|
|
460
|
+
return super().get(prop_name, default)
|
|
403
461
|
|
|
404
462
|
get_property = get
|
|
405
463
|
|
|
@@ -559,8 +617,8 @@ class DocumentProperties(AnnotationProperties):
|
|
|
559
617
|
self.location = input_dict.pop("location")
|
|
560
618
|
super()._deserialize(input_dict)
|
|
561
619
|
|
|
562
|
-
def _serialize(self,
|
|
563
|
-
serialized = super()._serialize()
|
|
620
|
+
def _serialize(self, *args, **kwargs) -> dict:
|
|
621
|
+
serialized = super()._serialize(**kwargs)
|
|
564
622
|
if "location_" in serialized:
|
|
565
623
|
serialized["location"] = serialized.pop("location_")
|
|
566
624
|
return serialized
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
The :mod:`mmif` module contains the classes used to represent a full MMIF
|
|
3
3
|
file as a live Python object.
|
|
4
4
|
|
|
5
|
+
The :class:`Mmif` class is a high-level container that provides convenient
|
|
6
|
+
string-based access to documents, views, and annotations via ``mmif[id]``.
|
|
7
|
+
The underlying ``documents`` and ``views`` attributes are list-like collections
|
|
8
|
+
that use integer indexing; use container-level access for ID-based lookups.
|
|
9
|
+
|
|
5
10
|
See the specification docs and the JSON Schema file for more information.
|
|
6
11
|
"""
|
|
7
12
|
|
|
@@ -139,8 +144,35 @@ class Mmif(MmifObject):
|
|
|
139
144
|
"""
|
|
140
145
|
MmifObject that represents a full MMIF file.
|
|
141
146
|
|
|
147
|
+
This is a high-level container object that provides convenient string-based
|
|
148
|
+
access to documents, views, and annotations using their IDs. The underlying
|
|
149
|
+
collections (``documents`` and ``views``) are list-like and use integer
|
|
150
|
+
indexing, but Mmif itself accepts string IDs for convenient access.
|
|
151
|
+
|
|
142
152
|
:param mmif_obj: the JSON data
|
|
143
153
|
:param validate: whether to validate the data against the MMIF JSON schema.
|
|
154
|
+
|
|
155
|
+
Examples
|
|
156
|
+
--------
|
|
157
|
+
Accessing objects by ID (high-level, convenient):
|
|
158
|
+
|
|
159
|
+
.. code-block:: python
|
|
160
|
+
|
|
161
|
+
mmif = Mmif(mmif_json)
|
|
162
|
+
doc = mmif['m1'] # Get document by ID
|
|
163
|
+
view = mmif['v1'] # Get view by ID
|
|
164
|
+
ann = mmif['v1:a1'] # Get annotation by long-form ID
|
|
165
|
+
|
|
166
|
+
# Safe access with default:
|
|
167
|
+
doc = mmif.get('m99', default=None)
|
|
168
|
+
|
|
169
|
+
Accessing via underlying lists (positional access):
|
|
170
|
+
|
|
171
|
+
.. code-block:: python
|
|
172
|
+
|
|
173
|
+
first_doc = mmif.documents[0] # First document
|
|
174
|
+
last_view = mmif.views[-1] # Last view
|
|
175
|
+
all_views = mmif.views[1:4] # Slice of views
|
|
144
176
|
"""
|
|
145
177
|
|
|
146
178
|
def __init__(self, mmif_obj: Optional[Union[bytes, str, dict]] = None, *, validate: bool = True) -> None:
|
|
@@ -179,17 +211,18 @@ class Mmif(MmifObject):
|
|
|
179
211
|
json_str = json.loads(json_str)
|
|
180
212
|
jsonschema.validators.validate(json_str, schema)
|
|
181
213
|
|
|
182
|
-
def serialize(self,
|
|
214
|
+
def serialize(self, sanitize: bool = False, autogenerate_capital_annotations: bool = True, **kwargs) -> str:
|
|
183
215
|
"""
|
|
184
216
|
Serializes the MMIF object to a JSON string.
|
|
185
217
|
|
|
186
|
-
:param sanitize: If True, performs some sanitization of before returning
|
|
218
|
+
:param sanitize: If True, performs some sanitization of before returning
|
|
187
219
|
the JSON string. See :meth:`sanitize` for details.
|
|
188
|
-
:param autogenerate_capital_annotations: If True, automatically convert
|
|
189
|
-
any "pending" temporary properties from `Document` objects to
|
|
190
|
-
`Annotation` objects. See :meth:`generate_capital_annotations` for
|
|
220
|
+
:param autogenerate_capital_annotations: If True, automatically convert
|
|
221
|
+
any "pending" temporary properties from `Document` objects to
|
|
222
|
+
`Annotation` objects. See :meth:`generate_capital_annotations` for
|
|
191
223
|
details.
|
|
192
|
-
:param
|
|
224
|
+
:param kwargs: Keyword arguments to pass to the parent's ``serialize``
|
|
225
|
+
method (e.g., ``pretty=True``, ``include_context=False``).
|
|
193
226
|
:return: JSON string of the MMIF object.
|
|
194
227
|
"""
|
|
195
228
|
if autogenerate_capital_annotations:
|
|
@@ -197,7 +230,7 @@ class Mmif(MmifObject):
|
|
|
197
230
|
# sanitization should be done after `Annotation` annotations are generated
|
|
198
231
|
if sanitize:
|
|
199
232
|
self.sanitize()
|
|
200
|
-
return super().serialize(
|
|
233
|
+
return super().serialize(**kwargs)
|
|
201
234
|
|
|
202
235
|
def _deserialize(self, input_dict: dict) -> None:
|
|
203
236
|
"""
|
|
@@ -266,8 +299,8 @@ class Mmif(MmifObject):
|
|
|
266
299
|
## caching alignments
|
|
267
300
|
if all(map(lambda x: x in alignment_ann.properties, ('source', 'target'))):
|
|
268
301
|
try:
|
|
269
|
-
source_ann = self
|
|
270
|
-
target_ann = self
|
|
302
|
+
source_ann = self.__getitem__(alignment_ann.get('source'))
|
|
303
|
+
target_ann = self.__getitem__(alignment_ann.get('target'))
|
|
271
304
|
if isinstance(source_ann, Annotation) and isinstance(target_ann, Annotation):
|
|
272
305
|
source_ann._cache_alignment(alignment_ann, target_ann)
|
|
273
306
|
target_ann._cache_alignment(alignment_ann, source_ann)
|
|
@@ -408,13 +441,13 @@ class Mmif(MmifObject):
|
|
|
408
441
|
"""
|
|
409
442
|
Appends a View object to the views list.
|
|
410
443
|
|
|
411
|
-
Fails if there is already a view with the same ID or a document
|
|
444
|
+
Fails if there is already a view with the same ID or a document
|
|
412
445
|
with the same ID in the MMIF object.
|
|
413
446
|
|
|
414
|
-
:param view: the
|
|
447
|
+
:param view: the View object to add
|
|
415
448
|
:param overwrite: if set to True, will overwrite
|
|
416
449
|
an existing view with the same ID
|
|
417
|
-
:raises KeyError: if ``overwrite`` is set to False and existing
|
|
450
|
+
:raises KeyError: if ``overwrite`` is set to False and existing
|
|
418
451
|
object (document or view) with the same ID exists
|
|
419
452
|
:return: None
|
|
420
453
|
"""
|
|
@@ -503,6 +536,7 @@ class Mmif(MmifObject):
|
|
|
503
536
|
Only top-level documents have locations, so we only check them.
|
|
504
537
|
|
|
505
538
|
:param m_type: the type to search for
|
|
539
|
+
:param path_only: if True, returns resolved file system paths instead of location URIs
|
|
506
540
|
:return: a list of the values of the location fields in the corresponding documents
|
|
507
541
|
"""
|
|
508
542
|
docs = [document for document in self.documents if document.is_type(m_type) and document.location is not None]
|
|
@@ -516,6 +550,7 @@ class Mmif(MmifObject):
|
|
|
516
550
|
Method to get the location of *first* document of given type.
|
|
517
551
|
|
|
518
552
|
:param m_type: the type to search for
|
|
553
|
+
:param path_only: if True, returns resolved file system path instead of location URI
|
|
519
554
|
:return: the value of the location field in the corresponding document
|
|
520
555
|
"""
|
|
521
556
|
# TODO (krim @ 8/10/20): Is returning the first location desirable?
|
|
@@ -568,6 +603,8 @@ class Mmif(MmifObject):
|
|
|
568
603
|
"""
|
|
569
604
|
Finds views where alignments between two given annotation types occurred.
|
|
570
605
|
|
|
606
|
+
:param at_type1: the first annotation type to search for alignments
|
|
607
|
+
:param at_type2: the second annotation type to search for alignments
|
|
571
608
|
:return: a dict that keyed by view IDs (str) and has lists of alignment Annotation objects as values.
|
|
572
609
|
"""
|
|
573
610
|
v_and_a = {}
|
|
@@ -586,7 +623,7 @@ class Mmif(MmifObject):
|
|
|
586
623
|
aligned_types = set()
|
|
587
624
|
for ann_id in [alignment['target'], alignment['source']]:
|
|
588
625
|
ann_id = cast(str, ann_id)
|
|
589
|
-
aligned_type = cast(Annotation, self
|
|
626
|
+
aligned_type = cast(Annotation, self.__getitem__(ann_id)).at_type
|
|
590
627
|
aligned_types.add(aligned_type)
|
|
591
628
|
aligned_types = list(aligned_types) # because membership check for sets also checks hash() values
|
|
592
629
|
if len(aligned_types) == 2 and at_type1 in aligned_types and at_type2 in aligned_types:
|
|
@@ -598,7 +635,7 @@ class Mmif(MmifObject):
|
|
|
598
635
|
def get_views_for_document(self, doc_id: str) -> List[View]:
|
|
599
636
|
"""
|
|
600
637
|
Returns the list of all views that have annotations anchored on a particular document.
|
|
601
|
-
Note that when the document is inside a view (generated during the
|
|
638
|
+
Note that when the document is inside a view (generated during the workflow's running),
|
|
602
639
|
doc_id must be prefixed with the view_id.
|
|
603
640
|
"""
|
|
604
641
|
views = []
|
|
@@ -691,8 +728,8 @@ class Mmif(MmifObject):
|
|
|
691
728
|
"""
|
|
692
729
|
Finds annotations that are anchored between the given time points.
|
|
693
730
|
|
|
694
|
-
:param start: the start time point
|
|
695
|
-
:param end: the end time point
|
|
731
|
+
:param start: the start time point
|
|
732
|
+
:param end: the end time point
|
|
696
733
|
:param time_unit: the unit of the input time points. Default is `ms`.
|
|
697
734
|
:param at_types: a list of annotation types to filter with. Any type in this list will be included in the return.
|
|
698
735
|
:return: an iterator of Annotation objects that are anchored between the given time points
|
|
@@ -725,12 +762,12 @@ class Mmif(MmifObject):
|
|
|
725
762
|
def _get_linear_anchor_point(self, ann: Annotation, targets_sorted=False, start: bool = True) -> Union[int, float]:
|
|
726
763
|
# TODO (krim @ 2/5/24): Update the return type once timeunits are unified to `ms` as integers (https://github.com/clamsproject/mmif/issues/192)
|
|
727
764
|
"""
|
|
728
|
-
Retrieves the anchor point of the annotation. Currently, this method only supports linear anchors,
|
|
765
|
+
Retrieves the anchor point of the annotation. Currently, this method only supports linear anchors,
|
|
729
766
|
namely time and text, hence does not work with spatial anchors (polygons or video-object).
|
|
730
|
-
|
|
767
|
+
|
|
731
768
|
:param ann: An Annotation object that has a linear anchor point. Namely, some subtypes of `Region` vocabulary type.
|
|
732
|
-
:param start: If True, returns the start anchor point. Otherwise, returns the end anchor point. N/A for `timePoint` anchors.
|
|
733
769
|
:param targets_sorted: If True, the method will assume that the targets are sorted in the order of the anchor points.
|
|
770
|
+
:param start: If True, returns the start anchor point. Otherwise, returns the end anchor point. N/A for `timePoint` anchors.
|
|
734
771
|
:return: the anchor point of the annotation. 1d for linear regions (time, text)
|
|
735
772
|
"""
|
|
736
773
|
props = ann.properties
|
|
@@ -745,10 +782,10 @@ class Mmif(MmifObject):
|
|
|
745
782
|
point = math.inf if start else -1
|
|
746
783
|
comp = min if start else max
|
|
747
784
|
for target_id in ann.get_property('targets'):
|
|
748
|
-
point = comp(point, self._get_linear_anchor_point(self
|
|
785
|
+
point = comp(point, self._get_linear_anchor_point(self.__getitem__(target_id), start=start))
|
|
749
786
|
return point
|
|
750
787
|
target_id = ann.get_property('targets')[0 if start else -1]
|
|
751
|
-
return self._get_linear_anchor_point(self
|
|
788
|
+
return self._get_linear_anchor_point(self.__getitem__(target_id), start=start)
|
|
752
789
|
elif (start and 'start' in props) or (not start and 'end' in props):
|
|
753
790
|
return ann.get_property('start' if start else 'end')
|
|
754
791
|
else:
|
|
@@ -766,50 +803,77 @@ class Mmif(MmifObject):
|
|
|
766
803
|
"""
|
|
767
804
|
return self._get_linear_anchor_point(annotation, start=False)
|
|
768
805
|
|
|
769
|
-
def __getitem__(self, item: str)
|
|
770
|
-
-> Union[Document, View, Annotation, MmifMetadata, DocumentsList, ViewsList]:
|
|
806
|
+
def __getitem__(self, item: str) -> Union[Document, View, Annotation, MmifMetadata]:
|
|
771
807
|
"""
|
|
772
|
-
|
|
773
|
-
|
|
808
|
+
High-level string-based access to MMIF objects by their IDs.
|
|
809
|
+
|
|
810
|
+
This method provides convenient access to documents, views, and annotations
|
|
811
|
+
using their string identifiers. For long-form annotation IDs (format: ``V:A``),
|
|
812
|
+
performs a two-level search through the specified view.
|
|
813
|
+
|
|
814
|
+
Note: This is a high-level convenience method on the Mmif container itself.
|
|
815
|
+
The underlying ``documents`` and ``views`` collections are list-like and
|
|
816
|
+
only support integer indexing.
|
|
774
817
|
|
|
775
|
-
:
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
:
|
|
818
|
+
:param item: An object identifier:
|
|
819
|
+
- Document ID (e.g., 'm1', 'd1')
|
|
820
|
+
- View ID (e.g., 'v1', 'v_0')
|
|
821
|
+
- Annotation ID in long form (e.g., 'v1:a1', 'v1:tf1')
|
|
822
|
+
- Attribute name (e.g., 'metadata', 'documents', 'views')
|
|
823
|
+
:return: The requested Document, View, Annotation, or attribute object
|
|
824
|
+
:raises KeyError: If the item is not found
|
|
825
|
+
|
|
826
|
+
Examples
|
|
827
|
+
--------
|
|
828
|
+
High-level access by ID:
|
|
829
|
+
|
|
830
|
+
.. code-block:: python
|
|
831
|
+
|
|
832
|
+
mmif = Mmif(mmif_json)
|
|
833
|
+
|
|
834
|
+
# Access documents:
|
|
835
|
+
doc = mmif['m1'] # Returns Document with ID 'm1'
|
|
836
|
+
|
|
837
|
+
# Access views:
|
|
838
|
+
view = mmif['v1'] # Returns View with ID 'v1'
|
|
839
|
+
|
|
840
|
+
# Access annotations (long-form ID):
|
|
841
|
+
ann = mmif['v1:a1'] # Returns Annotation from view v1
|
|
842
|
+
|
|
843
|
+
# Access attributes:
|
|
844
|
+
metadata = mmif['metadata'] # Returns MmifMetadata object
|
|
845
|
+
|
|
846
|
+
# Will raise KeyError:
|
|
847
|
+
doc = mmif['nonexistent'] # KeyError!
|
|
848
|
+
|
|
849
|
+
For list-style positional access, use the underlying collections:
|
|
850
|
+
|
|
851
|
+
.. code-block:: python
|
|
852
|
+
|
|
853
|
+
first_doc = mmif.documents[0] # Integer index
|
|
854
|
+
second_view = mmif.views[1] # Integer index
|
|
855
|
+
|
|
856
|
+
See Also
|
|
857
|
+
--------
|
|
858
|
+
get : Safe access with default value instead of raising KeyError
|
|
781
859
|
"""
|
|
782
860
|
if item in self._named_attributes():
|
|
783
|
-
return self.__dict__
|
|
861
|
+
return self.__dict__.__getitem__(item)
|
|
784
862
|
if self.id_delimiter in item:
|
|
785
863
|
vid, _ = item.split(self.id_delimiter, 1)
|
|
786
|
-
|
|
864
|
+
view = self.views._items.get(vid)
|
|
865
|
+
if view is None:
|
|
866
|
+
raise KeyError(f"View with ID {vid} not found in the MMIF object.")
|
|
867
|
+
ann = view.annotations._items.get(item)
|
|
868
|
+
if ann is None:
|
|
869
|
+
raise KeyError(f"Annotation with ID {item} not found in the MMIF object.")
|
|
870
|
+
return ann
|
|
787
871
|
else:
|
|
788
872
|
# search for document first, then views
|
|
789
873
|
# raise KeyError if nothing is found
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
return self.views.__getitem__(item)
|
|
795
|
-
except KeyError:
|
|
874
|
+
ret = self.documents._items.get(item)
|
|
875
|
+
if ret is None:
|
|
876
|
+
ret = self.views._items.get(item)
|
|
877
|
+
if ret is None:
|
|
796
878
|
raise KeyError(f"Object with ID {item} not found in the MMIF object. ")
|
|
797
|
-
|
|
798
|
-
def get(self, obj_id, default=None):
|
|
799
|
-
"""
|
|
800
|
-
High-level getter for Mmif. This will try to find any object, given
|
|
801
|
-
an identifier or an immediate attribute name. When nothing is found,
|
|
802
|
-
this will return a default value instead of raising an error.
|
|
803
|
-
|
|
804
|
-
:param obj_id: an immediate attribute name or an object identifier
|
|
805
|
-
(a document ID, a view ID, or an annotation ID). When
|
|
806
|
-
annotation ID is given as a "short" ID (without view
|
|
807
|
-
ID prefix), the method will try to find a match from
|
|
808
|
-
the first view, and return immediately if found.
|
|
809
|
-
:param default: the default value to return if none is found
|
|
810
|
-
:return: the object searched for or the default value
|
|
811
|
-
"""
|
|
812
|
-
try:
|
|
813
|
-
return self.__getitem__(obj_id)
|
|
814
|
-
except KeyError:
|
|
815
|
-
return default
|
|
879
|
+
return ret
|