mmif-python 1.5.3__tar.gz → 1.5.4__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.5.3 → mmif_python-1.5.4}/CHANGELOG.md +12 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/PKG-INFO +2 -2
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/target-versions.csv +1 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/serialize/annotation.py +125 -65
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/serialize/mmif.py +3 -17
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/serialize/view.py +17 -8
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/summarizer/nodes.py +64 -25
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/summarizer/summary.py +3 -3
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/video_document_helper.py +217 -37
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif_python.egg-info/PKG-INFO +2 -2
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif_python.egg-info/requires.txt +1 -1
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif_python.egg-info/scm_file_list.json +64 -64
- mmif_python-1.5.4/mmif_python.egg-info/scm_version.json +8 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/pyproject.toml +1 -1
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/test_serialize.py +135 -66
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/test_utils.py +425 -57
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/test_utils_cli.py +23 -0
- mmif_python-1.5.3/mmif_python.egg-info/scm_version.json +0 -8
- {mmif_python-1.5.3 → mmif_python-1.5.4}/.github/workflows/codecov.yml +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/.github/workflows/issue-assign.yml +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/.github/workflows/issue-close.yml +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/.github/workflows/issue-infra-project.yml +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/.github/workflows/publish.yml +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/.github/workflows/release-check.yml +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/.gitignore +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/CONTRIBUTING.md +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/LICENSE +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/README.md +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/build-tools/build.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/build-tools/clean.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/build-tools/docs.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/build-tools/prep_release.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/build-tools/publish.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/build-tools/test.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/_mmif_example_builder/__init__.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/_templates/versions.html +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/cli.rst +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/conf.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/consumer-tutorial.rst +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/index.rst +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/introduction.rst +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/plugins.rst +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/summarizer.rst +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/documentation/target-versions.rst +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/__init__.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/res/__init__.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/res/mmif.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/serialize/__init__.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/serialize/model.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/__init__.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/cli/__init__.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/cli/describe.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/cli/rewind.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/cli/source.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/cli/summarize.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/sequence_helper.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/summarizer/__init__.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/summarizer/config.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/summarizer/graph.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/summarizer/utils.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/text_document_helper.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/timeunit_helper.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/utils/workflow_helper.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/ver/__init__.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/vocabulary/__init__.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/vocabulary/annotation_types.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/vocabulary/base_types.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif/vocabulary/document_types.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif_docloc_http/__init__.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif_python.egg-info/SOURCES.txt +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif_python.egg-info/dependency_links.txt +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif_python.egg-info/entry_points.txt +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/mmif_python.egg-info/top_level.txt +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/setup.cfg +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/black-2997fps.mp4 +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/black-2997fps.webm +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/1.0.5-old-shortid.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/README.md +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/bars-tones-slates/raw.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/east-tesseract-typing/raw.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/everything/raw.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/examples.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/README.md +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/image-box-ocr-timex.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/image-box-ocr.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/image-box.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/image.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/slate-detect.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/slate-reco.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/video-demux.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/video-transcript-demux-fa.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/video-transcript-demux-fa.short.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/video-transcript-demux.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/video-transcript-sentencesplit.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/video-transcript.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/others/video.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/segmenter-kaldi-ner/raw.json +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif-examples/swt-1.0.mmif +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/mmif_examples.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/test_specver.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/test_vocab.py +0 -0
- {mmif_python-1.5.3 → mmif_python-1.5.4}/tests/testsrc-2997fps-ptsoffset.mp4 +0 -0
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
|
|
2
|
+
## releasing 1.5.3 (2026-07-08)
|
|
3
|
+
### Overview
|
|
4
|
+
This patch includes improvements in automatic conversion between contains-metadata properties and annotation properties (#396)
|
|
5
|
+
|
|
6
|
+
### Additions
|
|
7
|
+
* automatic "factoring" of value-shared properties from annotation-level to contains-level at serialization time works much more generally now (formerly only worked for `Annotation` type and `document` prop)
|
|
8
|
+
|
|
9
|
+
### Changes
|
|
10
|
+
* fixed a bug in reading old MMIF files with non-canonical prop names (contains-level prop value was not properly propagated at deserialization for those non-canonical named props)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
2
14
|
## releasing 1.5.2 (2026-07-07)
|
|
3
15
|
### Overview
|
|
4
16
|
This is a quick patch on the bug that when MMIF is deserialized, the annotation ID counts in the views weren't properly initialized. This should not be a real bug since new annotations should always written to new views (of current processing app) but not to existing views (where annotation count > 0), however, bugs in app code can trigger this condition, and this patch preventively fixes such cases.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mmif-python
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.4
|
|
4
4
|
Summary: Python implementation of MultiMedia Interchange Format specification. (https://mmif.clams.ai)
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: homepage, https://mmif.clams.ai
|
|
@@ -21,7 +21,7 @@ Requires-Dist: pillow; extra == "cv"
|
|
|
21
21
|
Requires-Dist: opencv-python; extra == "cv"
|
|
22
22
|
Requires-Dist: ffmpeg-python; extra == "cv"
|
|
23
23
|
Requires-Dist: wurlitzer; extra == "cv"
|
|
24
|
-
Requires-Dist: av; extra == "cv"
|
|
24
|
+
Requires-Dist: av>=15; extra == "cv"
|
|
25
25
|
Provides-Extra: seq
|
|
26
26
|
Requires-Dist: numpy; extra == "seq"
|
|
27
27
|
Provides-Extra: docs
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"``mmif-python`` version","Target MMIF Specification"
|
|
2
|
+
`1.5.4 <https://pypi.org/project/mmif-python/1.5.4/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
2
3
|
`1.5.3 <https://pypi.org/project/mmif-python/1.5.3/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
3
4
|
`1.5.2 <https://pypi.org/project/mmif-python/1.5.2/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
4
5
|
`1.5.1 <https://pypi.org/project/mmif-python/1.5.1/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
@@ -6,6 +6,7 @@ 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
|
"""
|
|
9
|
+
import functools
|
|
9
10
|
import importlib
|
|
10
11
|
import itertools
|
|
11
12
|
import os
|
|
@@ -22,7 +23,7 @@ from .model import MmifObject, PRMTV_TYPES
|
|
|
22
23
|
from .. import DocumentTypes, AnnotationTypes
|
|
23
24
|
import mmif_docloc_http
|
|
24
25
|
|
|
25
|
-
__all__ = ['Annotation', 'AnnotationProperties', 'Document', 'DocumentProperties', 'Text']
|
|
26
|
+
__all__ = ['Annotation', 'AnnotationProperties', 'Document', 'DocumentProperties', 'Text', 'canonicalize_prop_name']
|
|
26
27
|
|
|
27
28
|
T = TypeVar('T')
|
|
28
29
|
LIST_PRMTV = typing.List[PRMTV_TYPES] # list of values (most cases for annotation props)
|
|
@@ -41,6 +42,40 @@ discovered_docloc_plugins.update({
|
|
|
41
42
|
})
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
@functools.lru_cache(maxsize=None)
|
|
46
|
+
def _prop_alias_map(shortname: str) -> Dict[str, str]:
|
|
47
|
+
"""
|
|
48
|
+
Maps every spelling of an aliased property of a type to the canonical
|
|
49
|
+
name. The table is read from the latest version of the type in the
|
|
50
|
+
vocabulary (``AnnotationTypes.<shortname>``), which is the single source
|
|
51
|
+
of truth for property aliases.
|
|
52
|
+
|
|
53
|
+
:param shortname: shortname of the annotation type (e.g. ``TimeFrame``)
|
|
54
|
+
:return: ``{spelling: canonical_name}``, empty for types without aliases
|
|
55
|
+
"""
|
|
56
|
+
latest = getattr(AnnotationTypes, shortname, None)
|
|
57
|
+
groups = getattr(latest, '_property_aliases', None) or {}
|
|
58
|
+
return {alias: canonical for canonical, group in groups.items() for alias in group}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def canonicalize_prop_name(at_type: ThingTypesBase, prop_name: str) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Resolves a property name to its canonical spelling for the given type.
|
|
64
|
+
Property aliases were introduced in MMIF 1.0.2, when the general
|
|
65
|
+
``label`` property replaced ``frameType`` (``TimeFrame``) and ``boxType``
|
|
66
|
+
(``BoundingBox``). Names that are not aliases, and types without an alias
|
|
67
|
+
table, are returned unchanged.
|
|
68
|
+
|
|
69
|
+
:param at_type: the ``@type`` of the annotation
|
|
70
|
+
:param prop_name: a property name, canonical or deprecated
|
|
71
|
+
:return: the canonical property name
|
|
72
|
+
"""
|
|
73
|
+
shortname = getattr(at_type, 'shortname', None)
|
|
74
|
+
if not shortname:
|
|
75
|
+
return prop_name
|
|
76
|
+
return _prop_alias_map(shortname).get(prop_name, prop_name)
|
|
77
|
+
|
|
78
|
+
|
|
44
79
|
class Annotation(MmifObject):
|
|
45
80
|
"""
|
|
46
81
|
MmifObject that represents an annotation in a MMIF view.
|
|
@@ -49,11 +84,11 @@ class Annotation(MmifObject):
|
|
|
49
84
|
def __init__(self, anno_obj: Optional[Union[bytes, str, dict]] = None, *_) -> None:
|
|
50
85
|
self._type: ThingTypesBase = ThingTypesBase('')
|
|
51
86
|
# to store the parent view ID
|
|
52
|
-
self._props_ephemeral: AnnotationProperties = AnnotationProperties()
|
|
87
|
+
self._props_ephemeral: AnnotationProperties = AnnotationProperties(None, self)
|
|
53
88
|
self._alignments = {} # to hold alignment information (Alignment anno long_id -> aligned anno long_id)
|
|
54
89
|
self.reserved_names.update(('_props_ephemeral', '_alignments'))
|
|
55
90
|
if not hasattr(self, 'properties'): # don't overwrite DocumentProperties on super() call
|
|
56
|
-
self.properties: AnnotationProperties = AnnotationProperties()
|
|
91
|
+
self.properties: AnnotationProperties = AnnotationProperties(None, self)
|
|
57
92
|
self._attribute_classes = {'properties': AnnotationProperties}
|
|
58
93
|
self.disallow_additional_properties()
|
|
59
94
|
self._required_attributes = ["_type", "properties"]
|
|
@@ -69,9 +104,7 @@ class Annotation(MmifObject):
|
|
|
69
104
|
# here is the place to parse formatted IDs and store prefixes in the parent mmif object.
|
|
70
105
|
# (see https://github.com/clamsproject/mmif/issues/64#issuecomment-849241309 for discussion)
|
|
71
106
|
super()._deserialize(input_dict)
|
|
72
|
-
|
|
73
|
-
self._add_prop_aliases(k, v)
|
|
74
|
-
|
|
107
|
+
|
|
75
108
|
def _cache_alignment(self, alignment_ann: 'Annotation', alignedto_ann: 'Annotation') -> None:
|
|
76
109
|
"""
|
|
77
110
|
Cache alignment information. This cache will not be serialized.
|
|
@@ -107,29 +140,20 @@ class Annotation(MmifObject):
|
|
|
107
140
|
yield aligned
|
|
108
141
|
|
|
109
142
|
|
|
110
|
-
def
|
|
111
|
-
"""
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if alias in self.properties.keys():
|
|
125
|
-
warning_msg = (f'Both "{key_to_add}" and "{alias}" are in the properties of "{self.id}", '
|
|
126
|
-
f'however ')
|
|
127
|
-
if alias == alias_rep:
|
|
128
|
-
warning_msg += f'"{key_to_add}" is an alias of "{alias_rep}".'
|
|
129
|
-
else:
|
|
130
|
-
warning_msg += f'"{key_to_add}" and "{alias}" are both aliases of "{alias_rep}".'
|
|
131
|
-
warning_msg += f'Having two synonyms in the same annotation can cause unexpected behavior. '
|
|
132
|
-
warnings.warn(warning_msg, UserWarning)
|
|
143
|
+
def _add_ephemeral_defaults(self, defaults: typing.Mapping[str, typing.Any]) -> None:
|
|
144
|
+
"""
|
|
145
|
+
Distributes view-level defaults (from the ``contains`` metadata of the
|
|
146
|
+
parent view) to this annotation as ephemeral properties, under their
|
|
147
|
+
canonical names. An annotation-level value takes precedence over a
|
|
148
|
+
view-level default, so a default is skipped when the annotation
|
|
149
|
+
carries the property itself.
|
|
150
|
+
|
|
151
|
+
:param defaults: property defaults for this annotation's ``@type``
|
|
152
|
+
"""
|
|
153
|
+
for name, value in defaults.items():
|
|
154
|
+
name = canonicalize_prop_name(self._type, name)
|
|
155
|
+
if name not in self.properties:
|
|
156
|
+
self._props_ephemeral[name] = value
|
|
133
157
|
|
|
134
158
|
def is_type(self, at_type: Union[str, ThingTypesBase]) -> bool:
|
|
135
159
|
"""
|
|
@@ -239,7 +263,9 @@ class Annotation(MmifObject):
|
|
|
239
263
|
def add_property(self, name: str,
|
|
240
264
|
value: Union[PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]) -> None:
|
|
241
265
|
"""
|
|
242
|
-
Adds a property to the annotation's properties.
|
|
266
|
+
Adds a property to the annotation's properties. A deprecated spelling
|
|
267
|
+
of an aliased property (e.g. ``frameType``) is stored under its
|
|
268
|
+
canonical name (``label``), with a :class:`DeprecationWarning`.
|
|
243
269
|
|
|
244
270
|
:param name: the name of the property
|
|
245
271
|
:param value: the property's desired value
|
|
@@ -252,14 +278,14 @@ class Annotation(MmifObject):
|
|
|
252
278
|
# "either string, number, boolean, None, a JSON array of them, "
|
|
253
279
|
# "or a JSON object of them keyed by strings."
|
|
254
280
|
# f"(\"{name}\": \"{str(value)}\"")
|
|
255
|
-
self._add_prop_aliases(name, value)
|
|
256
281
|
|
|
257
282
|
def __getitem__(self, prop_name: str):
|
|
258
283
|
if prop_name in {'at_type', '@type'}:
|
|
259
284
|
return str(self._type)
|
|
260
285
|
elif prop_name == 'properties':
|
|
261
286
|
return self.properties
|
|
262
|
-
|
|
287
|
+
prop_name = canonicalize_prop_name(self._type, prop_name)
|
|
288
|
+
if prop_name in self.properties:
|
|
263
289
|
return self.properties[prop_name]
|
|
264
290
|
elif prop_name in self._props_ephemeral:
|
|
265
291
|
return self._props_ephemeral[prop_name]
|
|
@@ -279,7 +305,9 @@ class Annotation(MmifObject):
|
|
|
279
305
|
3. Special fields (``@type``, ``properties``)
|
|
280
306
|
|
|
281
307
|
This allows convenient access to properties without explicitly
|
|
282
|
-
checking the ``properties`` object or view-level metadata.
|
|
308
|
+
checking the ``properties`` object or view-level metadata. A
|
|
309
|
+
deprecated spelling of an aliased property (e.g. ``frameType``)
|
|
310
|
+
resolves to the canonical property (``label``).
|
|
283
311
|
|
|
284
312
|
:param prop_name: The name of the property to retrieve
|
|
285
313
|
:param default: The value to return if the property is not found (default: None)
|
|
@@ -318,23 +346,6 @@ class Annotation(MmifObject):
|
|
|
318
346
|
except KeyError:
|
|
319
347
|
return False
|
|
320
348
|
|
|
321
|
-
def _get_label(self) -> str:
|
|
322
|
-
"""
|
|
323
|
-
Another prototypical method to handle property aliases.
|
|
324
|
-
See :meth:`.Annotation._add_prop_aliases` for more details on
|
|
325
|
-
what property aliases are.
|
|
326
|
-
Not recommended to use this method as `_add_prop_aliases` method
|
|
327
|
-
is preferred.
|
|
328
|
-
"""
|
|
329
|
-
if 'label' in self:
|
|
330
|
-
return str(self.get('label'))
|
|
331
|
-
elif self._type.shortname == 'TimeFrame' and 'frameType' in self:
|
|
332
|
-
return str(self.get('frameType'))
|
|
333
|
-
elif self._type.shortname == 'BoundingBox' and 'boxType' in self:
|
|
334
|
-
return str(self.get('boxType'))
|
|
335
|
-
else:
|
|
336
|
-
raise KeyError("No label found in this annotation.")
|
|
337
|
-
|
|
338
349
|
def is_document(self):
|
|
339
350
|
return isinstance(self._type, DocumentTypesBase)
|
|
340
351
|
|
|
@@ -356,8 +367,8 @@ class Document(Annotation):
|
|
|
356
367
|
# see https://github.com/clamsproject/mmif-python/issues/226 for discussion
|
|
357
368
|
# around the use of these three dictionaries
|
|
358
369
|
# (names changed since, `existing` >> `ephemeral` and `temporary` >> `pending`)
|
|
359
|
-
self._props_original: DocumentProperties = DocumentProperties()
|
|
360
|
-
self._props_pending: AnnotationProperties = AnnotationProperties()
|
|
370
|
+
self._props_original: DocumentProperties = DocumentProperties(None, self)
|
|
371
|
+
self._props_pending: AnnotationProperties = AnnotationProperties(None, self)
|
|
361
372
|
self.reserved_names.update(('_props_original', '_props_pending'))
|
|
362
373
|
|
|
363
374
|
self._type: Union[ThingTypesBase, DocumentTypesBase] = ThingTypesBase('')
|
|
@@ -578,6 +589,7 @@ class AnnotationProperties(MmifObject, MutableMapping[str, T]):
|
|
|
578
589
|
"""
|
|
579
590
|
|
|
580
591
|
def __delitem__(self, key: str) -> None:
|
|
592
|
+
key = self._canonical(key)
|
|
581
593
|
frm = None
|
|
582
594
|
for k in self.__iter__():
|
|
583
595
|
if k == key:
|
|
@@ -593,36 +605,83 @@ class AnnotationProperties(MmifObject, MutableMapping[str, T]):
|
|
|
593
605
|
del frm[key]
|
|
594
606
|
else:
|
|
595
607
|
raise KeyError(f'Key "{key}" not found.')
|
|
596
|
-
|
|
608
|
+
|
|
597
609
|
def __iter__(self) -> Iterator[str]:
|
|
598
610
|
"""
|
|
599
|
-
``__iter__`` on Mapping should basically work as ``keys()`` method
|
|
611
|
+
``__iter__`` on Mapping should basically work as ``keys()`` method
|
|
600
612
|
of vanilla dict.
|
|
601
613
|
"""
|
|
602
614
|
for key in itertools.chain(self._named_attributes(), self._unnamed_attributes):
|
|
603
615
|
yield key
|
|
604
|
-
|
|
616
|
+
|
|
605
617
|
def __getitem__(self, key):
|
|
606
618
|
"""
|
|
607
|
-
Parent MmifObject class has a __getitem__ method that checks if
|
|
608
|
-
the value is empty when asked for an unnamed attribute. But for
|
|
609
|
-
AnnotationProperties, any arbitrary property that's added
|
|
610
|
-
explicitly by the user (developer) should not be ignored and
|
|
611
|
-
returned even the value is empty.
|
|
619
|
+
Parent MmifObject class has a __getitem__ method that checks if
|
|
620
|
+
the value is empty when asked for an unnamed attribute. But for
|
|
621
|
+
AnnotationProperties, any arbitrary property that's added
|
|
622
|
+
explicitly by the user (developer) should not be ignored and
|
|
623
|
+
returned even the value is empty.
|
|
612
624
|
"""
|
|
625
|
+
key = self._canonical(key)
|
|
613
626
|
if key in self._named_attributes():
|
|
614
627
|
return self.__dict__[key]
|
|
615
628
|
else:
|
|
616
629
|
return self._unnamed_attributes[key]
|
|
617
630
|
|
|
618
|
-
def
|
|
631
|
+
def __setitem__(self, key: str, value) -> None:
|
|
632
|
+
"""
|
|
633
|
+
Sets a property. A deprecated spelling of an aliased property (e.g.
|
|
634
|
+
``frameType``) is stored under its canonical name (``label``), with
|
|
635
|
+
a :class:`DeprecationWarning`.
|
|
636
|
+
"""
|
|
637
|
+
canonical = self._canonical(key)
|
|
638
|
+
if canonical != key:
|
|
639
|
+
warnings.warn(f'"{key}" is a deprecated alias of "{canonical}", storing the value as "{canonical}".',
|
|
640
|
+
DeprecationWarning)
|
|
641
|
+
super().__setitem__(canonical, value)
|
|
642
|
+
|
|
643
|
+
def __init__(self, mmif_obj: Optional[Union[bytes, str, dict]] = None,
|
|
644
|
+
parent: Optional['Annotation'] = None, *_) -> None:
|
|
619
645
|
self.id: str = ''
|
|
620
646
|
# any individual at_type (subclassing this class) can have its own set of required attributes
|
|
621
647
|
self._required_attributes = ["id"]
|
|
622
648
|
# allowing additional attributes for arbitrary annotation properties
|
|
623
649
|
self._unnamed_attributes = {}
|
|
650
|
+
# the Annotation these properties belong to; its `@type` decides which
|
|
651
|
+
# property names are aliases of each other (see `canonicalize_prop_name`)
|
|
652
|
+
self._parent_annotation = parent
|
|
653
|
+
self.reserved_names.add('_parent_annotation')
|
|
624
654
|
super().__init__(mmif_obj)
|
|
625
655
|
|
|
656
|
+
def _canonical(self, key: str) -> str:
|
|
657
|
+
if self._parent_annotation is None:
|
|
658
|
+
return key
|
|
659
|
+
return canonicalize_prop_name(self._parent_annotation._type, key)
|
|
660
|
+
|
|
661
|
+
def _deserialize(self, input_dict: dict) -> None:
|
|
662
|
+
"""
|
|
663
|
+
Deserializes the properties, rewriting deprecated spellings to their
|
|
664
|
+
canonical names so that there is one key per property. When two
|
|
665
|
+
spellings of the same property are present, the canonical one wins
|
|
666
|
+
(or the first one seen, when neither is canonical), with a warning.
|
|
667
|
+
"""
|
|
668
|
+
canonical_dict = {}
|
|
669
|
+
# canonical name -> the deprecated spelling it was read from
|
|
670
|
+
from_alias = {}
|
|
671
|
+
for key, value in input_dict.items():
|
|
672
|
+
canonical = self._canonical(key)
|
|
673
|
+
if canonical in canonical_dict and (canonical != key or canonical in from_alias):
|
|
674
|
+
alias = key if canonical != key else from_alias[canonical]
|
|
675
|
+
warnings.warn(f'Both "{alias}" and "{canonical}" are in the properties of '
|
|
676
|
+
f'"{input_dict.get("id", "")}", but "{alias}" is an alias of "{canonical}". '
|
|
677
|
+
f'Keeping the value of "{canonical}".', UserWarning)
|
|
678
|
+
if canonical != key:
|
|
679
|
+
continue
|
|
680
|
+
canonical_dict[canonical] = value
|
|
681
|
+
if canonical != key:
|
|
682
|
+
from_alias[canonical] = key
|
|
683
|
+
super()._deserialize(canonical_dict)
|
|
684
|
+
|
|
626
685
|
|
|
627
686
|
class DocumentProperties(AnnotationProperties):
|
|
628
687
|
"""
|
|
@@ -632,7 +691,8 @@ class DocumentProperties(AnnotationProperties):
|
|
|
632
691
|
:param mmif_obj: the JSON data that defines the properties
|
|
633
692
|
"""
|
|
634
693
|
|
|
635
|
-
def __init__(self, mmif_obj: Optional[Union[bytes, str, dict]] = None,
|
|
694
|
+
def __init__(self, mmif_obj: Optional[Union[bytes, str, dict]] = None,
|
|
695
|
+
parent: Optional['Annotation'] = None, *_) -> None:
|
|
636
696
|
self.mime: str = ''
|
|
637
697
|
# note the trailing underscore here. I wanted to use the name `location`
|
|
638
698
|
# for @property in this class and `Document` class, so had to use a diff
|
|
@@ -643,9 +703,9 @@ class DocumentProperties(AnnotationProperties):
|
|
|
643
703
|
self._attribute_classes = {'text': Text}
|
|
644
704
|
# in theory, either `location` or `text` should appear in a `document`
|
|
645
705
|
# but with current implementation, there's no easy way to set a condition
|
|
646
|
-
# for `oneOf` requirement
|
|
647
|
-
# see MmifObject::_required_attributes in model.py
|
|
648
|
-
super().__init__(mmif_obj)
|
|
706
|
+
# for `oneOf` requirement
|
|
707
|
+
# see MmifObject::_required_attributes in model.py
|
|
708
|
+
super().__init__(mmif_obj, parent)
|
|
649
709
|
|
|
650
710
|
def _deserialize(self, input_dict: dict) -> None:
|
|
651
711
|
if "location" in input_dict:
|
|
@@ -276,25 +276,11 @@ class Mmif(MmifObject):
|
|
|
276
276
|
super()._deserialize(input_dict)
|
|
277
277
|
for view in self.views:
|
|
278
278
|
view._parent_mmif = self
|
|
279
|
-
# this dict will be populated with properties
|
|
280
|
-
# that are not encoded in individual annotations objects themselves
|
|
281
|
-
extrinsic_props = defaultdict(dict)
|
|
282
|
-
for at_type, type_lv_props in view.metadata.contains.items():
|
|
283
|
-
for prop_key, prop_value in type_lv_props.items():
|
|
284
|
-
extrinsic_props[at_type][prop_key] = prop_value
|
|
285
279
|
for ann in view.get_annotations():
|
|
286
280
|
## for "capital" Annotation properties
|
|
287
|
-
# first
|
|
288
|
-
# as "ephemeral" properties
|
|
289
|
-
|
|
290
|
-
# in `_props_ephemeral` -- namely an alias of a property set on the
|
|
291
|
-
# annotation itself (put there by `_add_prop_aliases` during the
|
|
292
|
-
# annotation's own deserialization, which runs earlier). Per the
|
|
293
|
-
# spec, the annotation-level value takes precedence over the
|
|
294
|
-
# view-level default.
|
|
295
|
-
for prop_key, prop_value in extrinsic_props[ann.at_type].items():
|
|
296
|
-
if prop_key not in ann._props_ephemeral:
|
|
297
|
-
ann._props_ephemeral[prop_key] = prop_value
|
|
281
|
+
# first, distribute view-level defaults from `contains` metadata
|
|
282
|
+
# to the Annotation objects as "ephemeral" properties
|
|
283
|
+
ann._add_ephemeral_defaults(view.metadata.contains.get(ann.at_type, {}))
|
|
298
284
|
# then, do the same to associated Document objects. Note that,
|
|
299
285
|
# in a view, it is guaranteed that all Annotation objects are not duplicates
|
|
300
286
|
if ann.at_type == AnnotationTypes.Annotation:
|
|
@@ -17,7 +17,7 @@ from datetime import datetime
|
|
|
17
17
|
from typing import Dict, Union, Optional, Generator, List, cast
|
|
18
18
|
|
|
19
19
|
from mmif import DocumentTypes, AnnotationTypes, ThingTypesBase, ClamsTypesBase
|
|
20
|
-
from mmif.serialize.annotation import Annotation, Document
|
|
20
|
+
from mmif.serialize.annotation import Annotation, Document, canonicalize_prop_name
|
|
21
21
|
from mmif.serialize.model import PRMTV_TYPES, MmifObject, DataList, DataDict
|
|
22
22
|
from mmif.vocabulary.base_types import AnnotationTypesBase
|
|
23
23
|
|
|
@@ -197,8 +197,6 @@ class View(MmifObject):
|
|
|
197
197
|
self._set_ann_id(new_annotation, aid)
|
|
198
198
|
for propk, propv in properties.items():
|
|
199
199
|
new_annotation.add_property(propk, propv)
|
|
200
|
-
for propk, propv in self.metadata.contains.get(at_type, {}).items():
|
|
201
|
-
new_annotation._props_ephemeral[propk] = propv
|
|
202
200
|
return self.add_annotation(new_annotation, overwrite)
|
|
203
201
|
|
|
204
202
|
def add_annotation(self, annotation: 'Annotation', overwrite=False) -> 'Annotation':
|
|
@@ -222,6 +220,7 @@ class View(MmifObject):
|
|
|
222
220
|
annotation.id = f"{self.id}{self.id_delimiter}{annotation.id}"
|
|
223
221
|
self.annotations.append(annotation, overwrite)
|
|
224
222
|
self.new_contain(annotation.at_type)
|
|
223
|
+
annotation._add_ephemeral_defaults(self.metadata.contains.get(annotation.at_type, {}))
|
|
225
224
|
if annotation.at_type == AnnotationTypes.Alignment:
|
|
226
225
|
self._parent_mmif._cache_alignment(annotation)
|
|
227
226
|
return annotation
|
|
@@ -280,13 +279,14 @@ class View(MmifObject):
|
|
|
280
279
|
must match. Note that annotation type metadata are specified in the `contains` view metadata, not in individual \
|
|
281
280
|
annotation objects.
|
|
282
281
|
"""
|
|
283
|
-
def prop_check(k, v,
|
|
284
|
-
|
|
282
|
+
def prop_check(k, v, annotation, at_type_metadata):
|
|
283
|
+
k = canonicalize_prop_name(annotation.at_type, k)
|
|
284
|
+
return any(k in prop and prop[k] == v for prop in (annotation.properties, at_type_metadata))
|
|
285
285
|
|
|
286
286
|
for annotation in self.annotations:
|
|
287
287
|
at_type_metadata = self.metadata.contains.get(annotation.at_type, {})
|
|
288
288
|
if not at_type or (at_type and annotation.at_type == at_type):
|
|
289
|
-
if all(map(lambda kv: prop_check(kv[0], kv[1], annotation
|
|
289
|
+
if all(map(lambda kv: prop_check(kv[0], kv[1], annotation, at_type_metadata),
|
|
290
290
|
properties.items())):
|
|
291
291
|
yield annotation
|
|
292
292
|
|
|
@@ -485,7 +485,7 @@ class ViewMetadata(MmifObject):
|
|
|
485
485
|
at_type = ThingTypesBase.from_str(at_type)
|
|
486
486
|
|
|
487
487
|
if at_type not in self.contains:
|
|
488
|
-
new_contain = Contain(contains_metadata)
|
|
488
|
+
new_contain = Contain._for_type(at_type, contains_metadata)
|
|
489
489
|
self.add_contain(new_contain, at_type)
|
|
490
490
|
return new_contain
|
|
491
491
|
|
|
@@ -563,6 +563,15 @@ class Contain(DataDict[str, str]):
|
|
|
563
563
|
annotation type in the ``contains`` metadata of a MMIF view.
|
|
564
564
|
"""
|
|
565
565
|
|
|
566
|
+
@classmethod
|
|
567
|
+
def _for_type(cls, at_type: ThingTypesBase, contains_metadata: dict) -> 'Contain':
|
|
568
|
+
"""
|
|
569
|
+
Builds a ``Contain`` for the given ``@type``, with property names
|
|
570
|
+
rewritten to their canonical spellings (see
|
|
571
|
+
:func:`mmif.serialize.annotation.canonicalize_prop_name`).
|
|
572
|
+
"""
|
|
573
|
+
return cls({canonicalize_prop_name(at_type, k): v for k, v in contains_metadata.items()})
|
|
574
|
+
|
|
566
575
|
|
|
567
576
|
class AnnotationsList(DataList[Union[Annotation, Document]]):
|
|
568
577
|
"""
|
|
@@ -608,7 +617,7 @@ class ContainsDict(DataDict[ThingTypesBase, Contain]):
|
|
|
608
617
|
for key, value in input_dict.items():
|
|
609
618
|
if isinstance(key, str):
|
|
610
619
|
key = ThingTypesBase.from_str(key)
|
|
611
|
-
self._items[key] = Contain(value)
|
|
620
|
+
self._items[key] = Contain._for_type(key, value)
|
|
612
621
|
|
|
613
622
|
def update(self, other: Union[dict, 'ContainsDict'], overwrite=False):
|
|
614
623
|
for k, v in other.items():
|
|
@@ -1,10 +1,64 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from collections.abc import MutableMapping
|
|
2
3
|
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
6
|
+
from mmif.serialize.annotation import canonicalize_prop_name
|
|
7
|
+
from mmif.serialize.model import MmifObject
|
|
5
8
|
from mmif.utils.summarizer import config
|
|
6
9
|
|
|
7
10
|
|
|
11
|
+
class NodeProperties(MutableMapping):
|
|
12
|
+
|
|
13
|
+
"""Read-through view of an annotation's properties for use in a Node. Reads
|
|
14
|
+
resolve, in order, local writes made by the summarizer, the annotation's own
|
|
15
|
+
properties (deprecated spellings included, e.g. `word` for `text`), and the
|
|
16
|
+
view-level defaults for the annotation's type in the view's `contains`
|
|
17
|
+
metadata. Values are plain JSON types, as they would be after a serialization
|
|
18
|
+
round trip. Writes never reach the annotation; they are kept locally so that
|
|
19
|
+
summarizing leaves the loaded MMIF unchanged.
|
|
20
|
+
|
|
21
|
+
Note on the alternative: the SDK could instead copy `contains` defaults into
|
|
22
|
+
each annotation's `.properties` at deserialization and rely on
|
|
23
|
+
`Mmif.factor_out_shared_properties` to lift them back out on serialization.
|
|
24
|
+
That round trip is not shape-preserving when a type has a single annotation
|
|
25
|
+
in the view, when the key is non-factorable (`start`, `source`, `target`, ...),
|
|
26
|
+
or when another annotation of the type overrides the default, so the SDK
|
|
27
|
+
keeps defaults out of `.properties` and this class resolves them here."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, annotation, view):
|
|
30
|
+
self.annotation = annotation
|
|
31
|
+
contains = {} if view is None else view.metadata.contains.get(annotation.at_type, {})
|
|
32
|
+
self.defaults = {canonicalize_prop_name(annotation.at_type, k): v for k, v in contains.items()}
|
|
33
|
+
self.local = {}
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def _as_json(value):
|
|
37
|
+
return json.loads(str(value)) if isinstance(value, MmifObject) else value
|
|
38
|
+
|
|
39
|
+
def __getitem__(self, key):
|
|
40
|
+
if key in self.local:
|
|
41
|
+
return self.local[key]
|
|
42
|
+
if key in self.annotation.properties:
|
|
43
|
+
return self._as_json(self.annotation.properties[key])
|
|
44
|
+
return self.defaults[canonicalize_prop_name(self.annotation.at_type, key)]
|
|
45
|
+
|
|
46
|
+
def __setitem__(self, key, value):
|
|
47
|
+
self.local[key] = value
|
|
48
|
+
|
|
49
|
+
def __delitem__(self, key):
|
|
50
|
+
del self.local[key]
|
|
51
|
+
|
|
52
|
+
def __iter__(self):
|
|
53
|
+
seen = set()
|
|
54
|
+
for key in (*self.annotation.properties, *self.defaults, *self.local):
|
|
55
|
+
if key not in seen:
|
|
56
|
+
seen.add(key)
|
|
57
|
+
yield key
|
|
58
|
+
|
|
59
|
+
def __len__(self):
|
|
60
|
+
return sum(1 for _ in self)
|
|
61
|
+
|
|
8
62
|
|
|
9
63
|
class Node(object):
|
|
10
64
|
|
|
@@ -16,7 +70,7 @@ class Node(object):
|
|
|
16
70
|
# copy some information from the Annotation
|
|
17
71
|
self.at_type = annotation.at_type
|
|
18
72
|
self.identifier = annotation.id
|
|
19
|
-
self.properties =
|
|
73
|
+
self.properties = NodeProperties(annotation, view)
|
|
20
74
|
# get the document from the view or the properties
|
|
21
75
|
self.document = self._get_document()
|
|
22
76
|
# The targets property contains a list of annotations or documents that
|
|
@@ -151,23 +205,10 @@ class Node(object):
|
|
|
151
205
|
|
|
152
206
|
def _get_document(self):
|
|
153
207
|
"""Return the document or annotation node that the annotation/document in
|
|
154
|
-
the node refers to via the document property
|
|
155
|
-
|
|
156
|
-
if neither of those exist."""
|
|
157
|
-
# try the local property
|
|
208
|
+
the node refers to via the document property (the annotation's own, or the
|
|
209
|
+
view-level default). Return None if there is none."""
|
|
158
210
|
docid = self.properties.get('document')
|
|
159
|
-
if docid is
|
|
160
|
-
# print('>>>', docid, self.graph.get_node(docid))
|
|
161
|
-
return self.graph.get_node(docid)
|
|
162
|
-
# try the metadata property
|
|
163
|
-
if self.view is not None:
|
|
164
|
-
try:
|
|
165
|
-
metadata = self.view.metadata.contains[self.at_type]
|
|
166
|
-
docid = metadata['document']
|
|
167
|
-
return self.graph.get_node(docid)
|
|
168
|
-
except KeyError:
|
|
169
|
-
return None
|
|
170
|
-
return None
|
|
211
|
+
return None if docid is None else self.graph.get_node(docid)
|
|
171
212
|
|
|
172
213
|
def summary(self):
|
|
173
214
|
"""The default summary is just the identfier, this should typically be
|
|
@@ -197,9 +238,9 @@ class Node(object):
|
|
|
197
238
|
class TimeFrameNode(Node):
|
|
198
239
|
|
|
199
240
|
def __str__(self):
|
|
200
|
-
|
|
241
|
+
label = f' {self.label()}' if self.has_label() else ''
|
|
201
242
|
return ('<TimeFrameNode %s %s:%s%s>'
|
|
202
|
-
% (self.identifier, self.start(), self.end(),
|
|
243
|
+
% (self.identifier, self.start(), self.end(), label))
|
|
203
244
|
|
|
204
245
|
def start(self):
|
|
205
246
|
return self.properties.get('start', -1)
|
|
@@ -207,13 +248,11 @@ class TimeFrameNode(Node):
|
|
|
207
248
|
def end(self):
|
|
208
249
|
return self.properties.get('end', -1)
|
|
209
250
|
|
|
210
|
-
def
|
|
211
|
-
|
|
212
|
-
# NOTE: this is still aloowing for the old property though
|
|
213
|
-
return self.properties.get('label') or self.properties.get('frameType')
|
|
251
|
+
def label(self):
|
|
252
|
+
return self.properties.get('label')
|
|
214
253
|
|
|
215
254
|
def has_label(self):
|
|
216
|
-
return self.
|
|
255
|
+
return self.label() is not None
|
|
217
256
|
|
|
218
257
|
def representatives(self) -> list:
|
|
219
258
|
"""Return a list of the representative TimePoints."""
|
|
@@ -228,7 +267,7 @@ class TimeFrameNode(Node):
|
|
|
228
267
|
return { 'id': self.identifier,
|
|
229
268
|
'start': self.properties['start'],
|
|
230
269
|
'end': self.properties['end'],
|
|
231
|
-
'frameType': self.
|
|
270
|
+
'frameType': self.label() }
|
|
232
271
|
|
|
233
272
|
|
|
234
273
|
class EntityNode(Node):
|
|
@@ -147,7 +147,7 @@ class Annotations(object):
|
|
|
147
147
|
self.data = defaultdict(list)
|
|
148
148
|
# summary.graph.get_nodes(config.ANNOTATION, view_id=view.id)
|
|
149
149
|
for anno in summary.graph.get_nodes(config.ANNOTATION):
|
|
150
|
-
self.data[anno.view.id].append(anno.properties)
|
|
150
|
+
self.data[anno.view.id].append(dict(anno.properties))
|
|
151
151
|
|
|
152
152
|
def get(self, item):
|
|
153
153
|
return self.data.get(item, [])
|
|
@@ -300,7 +300,7 @@ class TranscriptElement:
|
|
|
300
300
|
# this adds the current token to the transcript
|
|
301
301
|
start = t.properties['start']
|
|
302
302
|
end = t.properties['end']
|
|
303
|
-
word = t.properties['
|
|
303
|
+
word = t.properties['text']
|
|
304
304
|
transcript.set_chars(word, start, end)
|
|
305
305
|
self.id = identifier
|
|
306
306
|
self.start = sentence[0].anchors['time-offsets'][0]
|
|
@@ -378,7 +378,7 @@ class TimeFrames(Nodes):
|
|
|
378
378
|
|
|
379
379
|
def _collect_timeframe_summaries(self):
|
|
380
380
|
for tf in self.nodes:
|
|
381
|
-
label = tf.
|
|
381
|
+
label = tf.label()
|
|
382
382
|
try:
|
|
383
383
|
start, end = tf.anchors['time-offsets']
|
|
384
384
|
except KeyError:
|