mmif-python 1.5.1__tar.gz → 1.5.3__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.1 → mmif_python-1.5.3}/CHANGELOG.md +11 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/CONTRIBUTING.md +1 -1
- {mmif_python-1.5.1 → mmif_python-1.5.3}/PKG-INFO +1 -1
- {mmif_python-1.5.1 → mmif_python-1.5.3}/build-tools/docs.py +23 -8
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/target-versions.csv +2 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/serialize/mmif.py +81 -14
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/serialize/view.py +19 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif_python.egg-info/PKG-INFO +1 -1
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif_python.egg-info/SOURCES.txt +2 -0
- mmif_python-1.5.3/mmif_python.egg-info/scm_file_list.json +95 -0
- mmif_python-1.5.3/mmif_python.egg-info/scm_version.json +8 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/test_serialize.py +177 -4
- {mmif_python-1.5.1 → mmif_python-1.5.3}/.github/workflows/codecov.yml +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/.github/workflows/issue-assign.yml +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/.github/workflows/issue-close.yml +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/.github/workflows/issue-infra-project.yml +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/.github/workflows/publish.yml +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/.github/workflows/release-check.yml +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/.gitignore +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/LICENSE +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/README.md +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/build-tools/build.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/build-tools/clean.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/build-tools/prep_release.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/build-tools/publish.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/build-tools/test.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/_mmif_example_builder/__init__.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/_templates/versions.html +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/cli.rst +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/conf.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/consumer-tutorial.rst +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/index.rst +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/introduction.rst +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/plugins.rst +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/summarizer.rst +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/documentation/target-versions.rst +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/__init__.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/res/__init__.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/res/mmif.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/serialize/__init__.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/serialize/annotation.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/serialize/model.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/__init__.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/cli/__init__.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/cli/describe.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/cli/rewind.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/cli/source.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/cli/summarize.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/sequence_helper.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/summarizer/__init__.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/summarizer/config.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/summarizer/graph.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/summarizer/nodes.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/summarizer/summary.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/summarizer/utils.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/text_document_helper.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/timeunit_helper.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/video_document_helper.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/utils/workflow_helper.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/ver/__init__.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/vocabulary/__init__.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/vocabulary/annotation_types.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/vocabulary/base_types.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif/vocabulary/document_types.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif_docloc_http/__init__.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif_python.egg-info/dependency_links.txt +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif_python.egg-info/entry_points.txt +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif_python.egg-info/requires.txt +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/mmif_python.egg-info/top_level.txt +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/pyproject.toml +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/setup.cfg +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/black-2997fps.mp4 +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/black-2997fps.webm +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/1.0.5-old-shortid.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/README.md +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/bars-tones-slates/raw.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/east-tesseract-typing/raw.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/everything/raw.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/examples.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/README.md +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/image-box-ocr-timex.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/image-box-ocr.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/image-box.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/image.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/slate-detect.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/slate-reco.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/video-demux.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/video-transcript-demux-fa.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/video-transcript-demux-fa.short.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/video-transcript-demux.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/video-transcript-sentencesplit.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/video-transcript.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/video.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/segmenter-kaldi-ner/raw.json +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/swt-1.0.mmif +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif_examples.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/test_specver.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/test_utils.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/test_utils_cli.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/test_vocab.py +0 -0
- {mmif_python-1.5.1 → mmif_python-1.5.3}/tests/testsrc-2997fps-ptsoffset.mp4 +0 -0
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
|
|
2
|
+
## releasing 1.5.2 (2026-07-07)
|
|
3
|
+
### Overview
|
|
4
|
+
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.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## releasing 1.5.1 (2026-06-19)
|
|
9
|
+
### Overview
|
|
10
|
+
This patch contains a bug fix in VDH that couldn't handle some mkv-contained video files (https://github.com/clamsproject/mmif-python/issues/390) , and some other minor documentation generation bugs.
|
|
11
|
+
|
|
12
|
+
|
|
2
13
|
## releasing 1.5.0 (2026-05-27)
|
|
3
14
|
### Overview
|
|
4
15
|
|
|
@@ -162,7 +162,7 @@ To build documentation for a specific historical version (e.g., `v1.0.0`):
|
|
|
162
162
|
python3 build-tools/docs.py --build-ver v1.0.0
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
This runs the build in a sandboxed temporary directory.
|
|
165
|
+
This runs the build in a sandboxed temporary directory. A single `--build-ver` places the built docs **flat** in the output directory (`docs-test/` by default). The batch `--build-all-since` mode instead nests each version under a `<version>/` subdirectory of the output directory, so multiple versions can coexist. The flat single-version layout is what the CI docs-publish workflow relies on — it invokes `docs.py --build-ver <ver> --output-dir _docs` and uploads `_docs/` as-is, without needing to know whether the project is versioned.
|
|
166
166
|
|
|
167
167
|
> [!NOTE]
|
|
168
168
|
> In CI, documentation is built and published automatically by the `publish.yml` workflow via the shared `sdk-docs.yml`. The CI calls `docs.py --build-ver <version> --output-dir _docs`. All CLAMS SDK repos use the same `docs.py` CLI interface (`--build-ver`, `--output-dir`).
|
|
@@ -188,7 +188,8 @@ def linkcode_resolve(domain, info):
|
|
|
188
188
|
return docs_build_dir
|
|
189
189
|
|
|
190
190
|
|
|
191
|
-
def build_docs_for_version(version: str, output_base_dir: Path, repo_path: Path = None
|
|
191
|
+
def build_docs_for_version(version: str, output_base_dir: Path, repo_path: Path = None,
|
|
192
|
+
versioned_subdir: bool = True):
|
|
192
193
|
"""
|
|
193
194
|
Builds docs for a specific version in a sandbox.
|
|
194
195
|
Uses local repo copy instead of cloning for efficiency.
|
|
@@ -197,6 +198,12 @@ def build_docs_for_version(version: str, output_base_dir: Path, repo_path: Path
|
|
|
197
198
|
version: Git tag/ref to build
|
|
198
199
|
output_base_dir: Directory to output built docs
|
|
199
200
|
repo_path: Path to local repo to copy from (defaults to cwd)
|
|
201
|
+
versioned_subdir: when True (batch/from-scratch rebuild), the built
|
|
202
|
+
docs are placed under ``output_base_dir/<version>/`` so multiple
|
|
203
|
+
versions can coexist. When False (single build, as used by CI),
|
|
204
|
+
the docs are placed flat into ``output_base_dir`` so the shared
|
|
205
|
+
docs-publish workflow can upload them as-is without knowing about
|
|
206
|
+
versioning.
|
|
200
207
|
"""
|
|
201
208
|
print(f"--- Running in Version Build Mode for version: {version} ---")
|
|
202
209
|
|
|
@@ -226,13 +233,19 @@ def build_docs_for_version(version: str, output_base_dir: Path, repo_path: Path
|
|
|
226
233
|
built_docs_path = build_versioned_docs(env, source_path, version)
|
|
227
234
|
|
|
228
235
|
print(f"\n--- Step 4: Copying built artifacts to output directory ---")
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
236
|
+
if versioned_subdir:
|
|
237
|
+
dest = output_base_dir / version
|
|
238
|
+
if dest.exists():
|
|
239
|
+
print(f"Removing existing directory: {dest}")
|
|
240
|
+
shutil.rmtree(dest)
|
|
241
|
+
shutil.copytree(built_docs_path, dest)
|
|
242
|
+
else:
|
|
243
|
+
# flat: place the built docs directly into output_base_dir
|
|
244
|
+
output_base_dir.mkdir(parents=True, exist_ok=True)
|
|
245
|
+
shutil.copytree(built_docs_path, output_base_dir, dirs_exist_ok=True)
|
|
246
|
+
dest = output_base_dir
|
|
234
247
|
|
|
235
|
-
print(f"\nDocumentation for {version} built successfully in: {
|
|
248
|
+
print(f"\nDocumentation for {version} built successfully in: {dest}")
|
|
236
249
|
|
|
237
250
|
|
|
238
251
|
def get_all_tags():
|
|
@@ -317,7 +330,9 @@ def main():
|
|
|
317
330
|
for version, error in failed:
|
|
318
331
|
print(f" {version}: {error}")
|
|
319
332
|
elif args.build_ver:
|
|
320
|
-
|
|
333
|
+
# single build (CI + local single-version): flat output so the
|
|
334
|
+
# shared docs-publish workflow uploads it version-agnostically
|
|
335
|
+
build_docs_for_version(args.build_ver, output_dir, versioned_subdir=False)
|
|
321
336
|
else:
|
|
322
337
|
build_docs_local(Path.cwd(), output_dir)
|
|
323
338
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"``mmif-python`` version","Target MMIF Specification"
|
|
2
|
+
`1.5.3 <https://pypi.org/project/mmif-python/1.5.3/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
3
|
+
`1.5.2 <https://pypi.org/project/mmif-python/1.5.2/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
2
4
|
`1.5.1 <https://pypi.org/project/mmif-python/1.5.1/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
3
5
|
`1.5.0 <https://pypi.org/project/mmif-python/1.5.0/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
4
6
|
`1.4.0 <https://pypi.org/project/mmif-python/1.4.0/>`__,`1.1.1 <https://mmif.clams.ai/1.1.1/>`__
|
|
@@ -175,6 +175,14 @@ class Mmif(MmifObject):
|
|
|
175
175
|
all_views = mmif.views[1:4] # Slice of views
|
|
176
176
|
"""
|
|
177
177
|
|
|
178
|
+
#: Hardcoded list of property names that must stay at the individual
|
|
179
|
+
#: annotation level, and never "factored" into the view's ``contains``
|
|
180
|
+
#: metadata, even when redundant across annotations.
|
|
181
|
+
_NON_FACTORABLE_PROPS = frozenset({
|
|
182
|
+
'start', 'end', 'targets', 'source', 'target', 'representatives',
|
|
183
|
+
'mime', 'location', 'location_', 'text',
|
|
184
|
+
})
|
|
185
|
+
|
|
178
186
|
def __init__(self, mmif_obj: Optional[Union[bytes, str, dict]] = None, *, validate: bool = True) -> None:
|
|
179
187
|
self.metadata: MmifMetadata = MmifMetadata()
|
|
180
188
|
self.documents: DocumentsList = DocumentsList()
|
|
@@ -211,26 +219,42 @@ class Mmif(MmifObject):
|
|
|
211
219
|
json_str = json.loads(json_str)
|
|
212
220
|
jsonschema.validators.validate(json_str, schema)
|
|
213
221
|
|
|
214
|
-
def serialize(self,
|
|
222
|
+
def serialize(self,
|
|
223
|
+
pretty: bool = False,
|
|
224
|
+
include_context: bool = True,
|
|
225
|
+
*,
|
|
226
|
+
sanitize: bool = False,
|
|
227
|
+
autogenerate_capital_annotations: bool = True,
|
|
228
|
+
factor_out_shared_properties: bool = True) -> str:
|
|
215
229
|
"""
|
|
216
230
|
Serializes the MMIF object to a JSON string.
|
|
217
231
|
|
|
232
|
+
:param pretty: If True, returns the string with indentation.
|
|
233
|
+
:param include_context: If False, excludes contextual attributes (e.g.
|
|
234
|
+
runtime timestamps) from serialization.
|
|
218
235
|
:param sanitize: If True, performs some sanitization of before returning
|
|
219
236
|
the JSON string. See :meth:`sanitize` for details.
|
|
220
237
|
:param autogenerate_capital_annotations: If True, automatically convert
|
|
221
238
|
any "pending" temporary properties from `Document` objects to
|
|
222
239
|
`Annotation` objects. See :meth:`generate_capital_annotations` for
|
|
223
240
|
details.
|
|
224
|
-
:param
|
|
225
|
-
|
|
241
|
+
:param factor_out_shared_properties: If True, lift properties shared
|
|
242
|
+
(same value) across all annotations of a type in a view into the
|
|
243
|
+
view's ``contains`` metadata. See
|
|
244
|
+
:meth:`factor_out_shared_properties` for details. Independent of
|
|
245
|
+
``autogenerate_capital_annotations``, but runs after it (so
|
|
246
|
+
freshly-materialized pending properties are included) when both are
|
|
247
|
+
enabled.
|
|
226
248
|
:return: JSON string of the MMIF object.
|
|
227
249
|
"""
|
|
228
250
|
if autogenerate_capital_annotations:
|
|
229
251
|
self.generate_capital_annotations()
|
|
252
|
+
if factor_out_shared_properties:
|
|
253
|
+
self.factor_out_shared_properties()
|
|
230
254
|
# sanitization should be done after `Annotation` annotations are generated
|
|
231
255
|
if sanitize:
|
|
232
256
|
self.sanitize()
|
|
233
|
-
return super().serialize(
|
|
257
|
+
return super().serialize(pretty=pretty, include_context=include_context)
|
|
234
258
|
|
|
235
259
|
def _deserialize(self, input_dict: dict) -> None:
|
|
236
260
|
"""
|
|
@@ -261,9 +285,16 @@ class Mmif(MmifObject):
|
|
|
261
285
|
for ann in view.get_annotations():
|
|
262
286
|
## for "capital" Annotation properties
|
|
263
287
|
# first add all extrinsic properties to the Annotation objects
|
|
264
|
-
# as "ephemeral" properties
|
|
288
|
+
# as "ephemeral" properties. A view-level `contains` default must
|
|
289
|
+
# not override an annotation-level value that is already present
|
|
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.
|
|
265
295
|
for prop_key, prop_value in extrinsic_props[ann.at_type].items():
|
|
266
|
-
ann._props_ephemeral
|
|
296
|
+
if prop_key not in ann._props_ephemeral:
|
|
297
|
+
ann._props_ephemeral[prop_key] = prop_value
|
|
267
298
|
# then, do the same to associated Document objects. Note that,
|
|
268
299
|
# in a view, it is guaranteed that all Annotation objects are not duplicates
|
|
269
300
|
if ann.at_type == AnnotationTypes.Annotation:
|
|
@@ -297,7 +328,7 @@ class Mmif(MmifObject):
|
|
|
297
328
|
f"Alignment {alignment_ann.id} has `source` and `target` properties that do not point to Annotation objects.",
|
|
298
329
|
RuntimeWarning)
|
|
299
330
|
## caching alignments
|
|
300
|
-
if all(map(lambda x: x in alignment_ann
|
|
331
|
+
if all(map(lambda x: x in alignment_ann, ('source', 'target'))):
|
|
301
332
|
try:
|
|
302
333
|
source_ann = self.__getitem__(alignment_ann.get('source'))
|
|
303
334
|
target_ann = self.__getitem__(alignment_ann.get('target'))
|
|
@@ -383,15 +414,51 @@ class Mmif(MmifObject):
|
|
|
383
414
|
if view_to_write.metadata.app == current_app and doc_id in view_to_write.annotations:
|
|
384
415
|
view_to_write[doc_id].properties.update(props)
|
|
385
416
|
else:
|
|
386
|
-
|
|
387
|
-
# if there's only one document, we can record the doc_id in the contains metadata
|
|
388
|
-
view_to_write.metadata.new_contain(AnnotationTypes.Annotation, document=doc_id)
|
|
389
|
-
props.pop('document', None)
|
|
390
|
-
else:
|
|
391
|
-
# otherwise, doc_id needs to be recorded in the annotation property
|
|
392
|
-
props['document'] = doc_id
|
|
417
|
+
props['document'] = doc_id
|
|
393
418
|
view_to_write.new_annotation(AnnotationTypes.Annotation, **props)
|
|
394
419
|
|
|
420
|
+
|
|
421
|
+
def factor_out_shared_properties(self):
|
|
422
|
+
"""
|
|
423
|
+
Factor properties that are shared (identical value) across all
|
|
424
|
+
annotations of a type within a view up into that view's ``contains``
|
|
425
|
+
metadata, per the MMIF spec recommendation to prefer view-level
|
|
426
|
+
defaults for properties shared among annotations of a type.
|
|
427
|
+
|
|
428
|
+
A property is factored when it is present, with the same value, on
|
|
429
|
+
**every** annotation of a given ``@type`` in the view, there are at
|
|
430
|
+
least two such annotations (so the value is genuinely shared), and the
|
|
431
|
+
key is not in the :attr:`_NON_FACTORABLE_PROPS` protection list.
|
|
432
|
+
Factored values are mirrored into each annotation's ephemeral props at
|
|
433
|
+
deserialization time, so in-memory access is unchanged, while the
|
|
434
|
+
serialized output carries the value once, in ``contains``.
|
|
435
|
+
"""
|
|
436
|
+
for view in self.views:
|
|
437
|
+
by_type = defaultdict(list)
|
|
438
|
+
for ann in view.get_annotations():
|
|
439
|
+
by_type[ann.at_type].append(ann)
|
|
440
|
+
for at_type, anns in by_type.items():
|
|
441
|
+
if len(anns) < 2:
|
|
442
|
+
continue
|
|
443
|
+
shared = {}
|
|
444
|
+
for key in list(anns[0].properties.keys()):
|
|
445
|
+
if key in self._NON_FACTORABLE_PROPS:
|
|
446
|
+
continue
|
|
447
|
+
value = anns[0].properties[key]
|
|
448
|
+
if all(key in a.properties and a.properties[key] == value
|
|
449
|
+
for a in anns):
|
|
450
|
+
shared[key] = value
|
|
451
|
+
if not shared:
|
|
452
|
+
continue
|
|
453
|
+
if at_type not in view.metadata.contains:
|
|
454
|
+
view.metadata.new_contain(at_type)
|
|
455
|
+
contain = view.metadata.contains[at_type]
|
|
456
|
+
for key, value in shared.items():
|
|
457
|
+
contain[key] = value
|
|
458
|
+
for a in anns:
|
|
459
|
+
del a.properties[key]
|
|
460
|
+
a._props_ephemeral[key] = value
|
|
461
|
+
|
|
395
462
|
def sanitize(self):
|
|
396
463
|
"""
|
|
397
464
|
Sanitizes a Mmif object by running some safeguards.
|
|
@@ -82,6 +82,25 @@ class View(MmifObject):
|
|
|
82
82
|
self._required_attributes = ["id", "metadata", "annotations"]
|
|
83
83
|
super().__init__(view_obj)
|
|
84
84
|
self._fix_old_short_ids()
|
|
85
|
+
self._rebuild_id_counts()
|
|
86
|
+
|
|
87
|
+
def _rebuild_id_counts(self):
|
|
88
|
+
"""
|
|
89
|
+
Re-seed the per-view auto-ID counters from the annotations loaded during
|
|
90
|
+
deserialization.
|
|
91
|
+
|
|
92
|
+
``_id_counts`` is not serialized, so a view read back from JSON starts
|
|
93
|
+
with an empty counter. Without this, the next :meth:`new_annotation`
|
|
94
|
+
regenerates an ID (e.g. ``an_1``) that already exists in the
|
|
95
|
+
deserialized view and :meth:`AnnotationsList.append` raises ``KeyError``.
|
|
96
|
+
See issue #393.
|
|
97
|
+
"""
|
|
98
|
+
for annotation in self.annotations:
|
|
99
|
+
local_id = annotation.id.split(self.id_delimiter)[-1]
|
|
100
|
+
prefix, _, num = local_id.rpartition('_')
|
|
101
|
+
if prefix and num.isdigit():
|
|
102
|
+
self._id_counts[prefix] = max(
|
|
103
|
+
self._id_counts.get(prefix, 0), int(num))
|
|
85
104
|
|
|
86
105
|
def _fix_old_short_ids(self):
|
|
87
106
|
"""
|
|
@@ -63,6 +63,8 @@ mmif_python.egg-info/SOURCES.txt
|
|
|
63
63
|
mmif_python.egg-info/dependency_links.txt
|
|
64
64
|
mmif_python.egg-info/entry_points.txt
|
|
65
65
|
mmif_python.egg-info/requires.txt
|
|
66
|
+
mmif_python.egg-info/scm_file_list.json
|
|
67
|
+
mmif_python.egg-info/scm_version.json
|
|
66
68
|
mmif_python.egg-info/top_level.txt
|
|
67
69
|
tests/black-2997fps.mp4
|
|
68
70
|
tests/black-2997fps.webm
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"README.md",
|
|
4
|
+
"LICENSE",
|
|
5
|
+
"pyproject.toml",
|
|
6
|
+
"CHANGELOG.md",
|
|
7
|
+
"CONTRIBUTING.md",
|
|
8
|
+
".gitignore",
|
|
9
|
+
"build-tools/publish.py",
|
|
10
|
+
"build-tools/prep_release.py",
|
|
11
|
+
"build-tools/build.py",
|
|
12
|
+
"build-tools/docs.py",
|
|
13
|
+
"build-tools/clean.py",
|
|
14
|
+
"build-tools/test.py",
|
|
15
|
+
"documentation/target-versions.csv",
|
|
16
|
+
"documentation/consumer-tutorial.rst",
|
|
17
|
+
"documentation/target-versions.rst",
|
|
18
|
+
"documentation/index.rst",
|
|
19
|
+
"documentation/introduction.rst",
|
|
20
|
+
"documentation/cli.rst",
|
|
21
|
+
"documentation/plugins.rst",
|
|
22
|
+
"documentation/summarizer.rst",
|
|
23
|
+
"documentation/conf.py",
|
|
24
|
+
"documentation/_mmif_example_builder/__init__.py",
|
|
25
|
+
"documentation/_templates/versions.html",
|
|
26
|
+
"mmif/__init__.py",
|
|
27
|
+
"mmif/vocabulary/__init__.py",
|
|
28
|
+
"mmif/vocabulary/base_types.py",
|
|
29
|
+
"mmif/vocabulary/document_types.py",
|
|
30
|
+
"mmif/vocabulary/annotation_types.py",
|
|
31
|
+
"mmif/utils/__init__.py",
|
|
32
|
+
"mmif/utils/text_document_helper.py",
|
|
33
|
+
"mmif/utils/timeunit_helper.py",
|
|
34
|
+
"mmif/utils/workflow_helper.py",
|
|
35
|
+
"mmif/utils/video_document_helper.py",
|
|
36
|
+
"mmif/utils/sequence_helper.py",
|
|
37
|
+
"mmif/utils/summarizer/__init__.py",
|
|
38
|
+
"mmif/utils/summarizer/config.py",
|
|
39
|
+
"mmif/utils/summarizer/utils.py",
|
|
40
|
+
"mmif/utils/summarizer/summary.py",
|
|
41
|
+
"mmif/utils/summarizer/graph.py",
|
|
42
|
+
"mmif/utils/summarizer/nodes.py",
|
|
43
|
+
"mmif/utils/cli/__init__.py",
|
|
44
|
+
"mmif/utils/cli/source.py",
|
|
45
|
+
"mmif/utils/cli/rewind.py",
|
|
46
|
+
"mmif/utils/cli/summarize.py",
|
|
47
|
+
"mmif/utils/cli/describe.py",
|
|
48
|
+
"mmif/serialize/model.py",
|
|
49
|
+
"mmif/serialize/__init__.py",
|
|
50
|
+
"mmif/serialize/view.py",
|
|
51
|
+
"mmif/serialize/annotation.py",
|
|
52
|
+
"mmif/serialize/mmif.py",
|
|
53
|
+
"mmif/res/__init__.py",
|
|
54
|
+
"mmif/res/mmif.json",
|
|
55
|
+
"mmif/ver/__init__.py",
|
|
56
|
+
"tests/mmif_examples.py",
|
|
57
|
+
"tests/test_specver.py",
|
|
58
|
+
"tests/test_utils.py",
|
|
59
|
+
"tests/test_utils_cli.py",
|
|
60
|
+
"tests/test_vocab.py",
|
|
61
|
+
"tests/testsrc-2997fps-ptsoffset.mp4",
|
|
62
|
+
"tests/test_serialize.py",
|
|
63
|
+
"tests/black-2997fps.mp4",
|
|
64
|
+
"tests/black-2997fps.webm",
|
|
65
|
+
"tests/mmif-examples/README.md",
|
|
66
|
+
"tests/mmif-examples/examples.json",
|
|
67
|
+
"tests/mmif-examples/1.0.5-old-shortid.json",
|
|
68
|
+
"tests/mmif-examples/swt-1.0.mmif",
|
|
69
|
+
"tests/mmif-examples/segmenter-kaldi-ner/raw.json",
|
|
70
|
+
"tests/mmif-examples/everything/raw.json",
|
|
71
|
+
"tests/mmif-examples/others/video-transcript.json",
|
|
72
|
+
"tests/mmif-examples/others/image-box-ocr-timex.json",
|
|
73
|
+
"tests/mmif-examples/others/README.md",
|
|
74
|
+
"tests/mmif-examples/others/video.json",
|
|
75
|
+
"tests/mmif-examples/others/video-transcript-demux-fa.json",
|
|
76
|
+
"tests/mmif-examples/others/image.json",
|
|
77
|
+
"tests/mmif-examples/others/video-transcript-sentencesplit.json",
|
|
78
|
+
"tests/mmif-examples/others/video-transcript-demux-fa.short.json",
|
|
79
|
+
"tests/mmif-examples/others/slate-detect.json",
|
|
80
|
+
"tests/mmif-examples/others/image-box.json",
|
|
81
|
+
"tests/mmif-examples/others/slate-reco.json",
|
|
82
|
+
"tests/mmif-examples/others/video-transcript-demux.json",
|
|
83
|
+
"tests/mmif-examples/others/image-box-ocr.json",
|
|
84
|
+
"tests/mmif-examples/others/video-demux.json",
|
|
85
|
+
"tests/mmif-examples/bars-tones-slates/raw.json",
|
|
86
|
+
"tests/mmif-examples/east-tesseract-typing/raw.json",
|
|
87
|
+
"mmif_docloc_http/__init__.py",
|
|
88
|
+
".github/workflows/issue-close.yml",
|
|
89
|
+
".github/workflows/codecov.yml",
|
|
90
|
+
".github/workflows/release-check.yml",
|
|
91
|
+
".github/workflows/issue-infra-project.yml",
|
|
92
|
+
".github/workflows/issue-assign.yml",
|
|
93
|
+
".github/workflows/publish.yml"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
@@ -76,7 +76,7 @@ class TestMmif(unittest.TestCase):
|
|
|
76
76
|
mmif_obj = Mmif(example)
|
|
77
77
|
except ValidationError:
|
|
78
78
|
self.fail(f"example {i}")
|
|
79
|
-
self.assertEqual(mmif_obj.serialize(True), Mmif(mmif_obj.serialize()).serialize(True), f'Failed on {i}')
|
|
79
|
+
self.assertEqual(mmif_obj.serialize(pretty=True), Mmif(mmif_obj.serialize()).serialize(pretty=True), f'Failed on {i}')
|
|
80
80
|
|
|
81
81
|
def test_json_mmif_deserialize(self):
|
|
82
82
|
for i, example in MMIF_EXAMPLES.items():
|
|
@@ -91,7 +91,7 @@ class TestMmif(unittest.TestCase):
|
|
|
91
91
|
for annotation in view.annotations:
|
|
92
92
|
self.assertIn('_type', annotation.__dict__)
|
|
93
93
|
self.assertTrue('id' in list(mmif_obj.views._items.values())[0].__dict__)
|
|
94
|
-
self.assertEqual(mmif_obj.serialize(True), Mmif(json.loads(mmif_obj.serialize())).serialize(True), f'Failed on {i}')
|
|
94
|
+
self.assertEqual(mmif_obj.serialize(pretty=True), Mmif(json.loads(mmif_obj.serialize())).serialize(pretty=True), f'Failed on {i}')
|
|
95
95
|
|
|
96
96
|
def test_str_vs_json_deserialize(self):
|
|
97
97
|
for i, example in MMIF_EXAMPLES.items():
|
|
@@ -99,7 +99,7 @@ class TestMmif(unittest.TestCase):
|
|
|
99
99
|
continue
|
|
100
100
|
str_mmif_obj = Mmif(example)
|
|
101
101
|
json_mmif_obj = Mmif(json.loads(example))
|
|
102
|
-
self.assertEqual(str_mmif_obj.serialize(True), json_mmif_obj.serialize(True), f'Failed on {i}')
|
|
102
|
+
self.assertEqual(str_mmif_obj.serialize(pretty=True), json_mmif_obj.serialize(pretty=True), f'Failed on {i}')
|
|
103
103
|
|
|
104
104
|
def test_bad_mmif_deserialize_no_metadata(self):
|
|
105
105
|
self.mmif_examples_json['everything'].pop('metadata')
|
|
@@ -1194,6 +1194,35 @@ class TestAnnotation(unittest.TestCase):
|
|
|
1194
1194
|
self.assertTrue("nonspeech", tf3.get_property('label'))
|
|
1195
1195
|
self.assertTrue("speech", tf3.get_property('frameLabel'))
|
|
1196
1196
|
|
|
1197
|
+
def test_alias_precedes_view_contains_default(self):
|
|
1198
|
+
# An annotation that sets a property via a deprecated alias (frameType
|
|
1199
|
+
# aliases label) provides an annotation-level value. A view-level
|
|
1200
|
+
# `contains` default for the canonical name must NOT override it -- the
|
|
1201
|
+
# annotation-level value takes precedence over the view-level default.
|
|
1202
|
+
tf = "http://mmif.clams.ai/vocabulary/TimeFrame/v6"
|
|
1203
|
+
raw = {
|
|
1204
|
+
"metadata": {"mmif": "http://mmif.clams.ai/1.2.0"},
|
|
1205
|
+
"documents": [{"@type": "http://mmif.clams.ai/vocabulary/VideoDocument/v1",
|
|
1206
|
+
"properties": {"id": "m1", "mime": "video/mp4",
|
|
1207
|
+
"location": "file:///v.mp4"}}],
|
|
1208
|
+
"views": [{"id": "v1",
|
|
1209
|
+
"metadata": {"app": "http://x/1", "contains": {tf: {"label": "X"}}},
|
|
1210
|
+
"annotations": [{"@type": tf,
|
|
1211
|
+
"properties": {"id": "v1:a1", "start": 0,
|
|
1212
|
+
"end": 5, "frameType": "bars"}}]}]
|
|
1213
|
+
}
|
|
1214
|
+
mmif_obj = Mmif(json.dumps(raw), validate=False)
|
|
1215
|
+
ann = mmif_obj['v1:a1']
|
|
1216
|
+
# the annotation's own frameType (alias of label) wins over contains
|
|
1217
|
+
self.assertEqual('bars', ann.get_property('label'))
|
|
1218
|
+
self.assertEqual('bars', ann.get_property('frameType'))
|
|
1219
|
+
# a genuine view-level default (not shadowed by any annotation value)
|
|
1220
|
+
# still applies
|
|
1221
|
+
raw['views'][0]['metadata']['contains'][tf] = {"timeUnit": "milliseconds"}
|
|
1222
|
+
raw['views'][0]['annotations'][0]['properties'] = {"id": "v1:a1", "start": 0, "end": 5}
|
|
1223
|
+
ann = Mmif(json.dumps(raw), validate=False)['v1:a1']
|
|
1224
|
+
self.assertEqual('milliseconds', ann.get_property('timeUnit'))
|
|
1225
|
+
|
|
1197
1226
|
def test_change_id(self):
|
|
1198
1227
|
anno_obj: Annotation = self.data['everything']['mmif']['v5:bb1']
|
|
1199
1228
|
|
|
@@ -1451,7 +1480,151 @@ class TestDocument(unittest.TestCase):
|
|
|
1451
1480
|
'author'))
|
|
1452
1481
|
self.assertEqual('you', list(mmif_roundtrip2.views.get_last_contentful_view().get_annotations(AnnotationTypes.Annotation))[
|
|
1453
1482
|
0].get_property('author'))
|
|
1454
|
-
|
|
1483
|
+
|
|
1484
|
+
def test_view_id_counts_rebuilt_on_deserialize(self):
|
|
1485
|
+
# regression for issue #393: `_id_counts` is not serialized, so after
|
|
1486
|
+
# deserialization the next generated id must not collide with an existing
|
|
1487
|
+
# one already present in the view.
|
|
1488
|
+
mmif = Mmif(validate=False)
|
|
1489
|
+
doc = Document(); doc.at_type = DocumentTypes.VideoDocument
|
|
1490
|
+
doc.id = 'd1'; doc.location = 'file:///v.mp4'
|
|
1491
|
+
mmif.add_document(doc)
|
|
1492
|
+
v = mmif.new_view()
|
|
1493
|
+
v.metadata.app = tester_appname
|
|
1494
|
+
a1 = v.new_annotation(AnnotationTypes.Annotation, document='d1', frameCount=10)
|
|
1495
|
+
self.assertTrue(a1.id.endswith('an_1'))
|
|
1496
|
+
# round-trip: the reloaded view still contains `an_1` but starts with an
|
|
1497
|
+
# empty counter unless rebuilt
|
|
1498
|
+
v2 = Mmif(mmif.serialize())[v.id]
|
|
1499
|
+
a2 = v2.new_annotation(AnnotationTypes.Annotation, document='d1', duration=20)
|
|
1500
|
+
self.assertTrue(a2.id.endswith('an_2')) # must not regenerate an_1
|
|
1501
|
+
|
|
1502
|
+
def test_capital_annotation_no_collision_across_apps(self):
|
|
1503
|
+
# regression for issue #393: a downstream app re-deriving a document
|
|
1504
|
+
# property with a *different* value must not crash on serialize; the
|
|
1505
|
+
# distinct value is recorded as an additional capital annotation
|
|
1506
|
+
# (value-based de-dup preserves both assertions), not a colliding one.
|
|
1507
|
+
m = Mmif(validate=False)
|
|
1508
|
+
d = Document()
|
|
1509
|
+
d.at_type = DocumentTypes.VideoDocument
|
|
1510
|
+
d.id = 'd1'
|
|
1511
|
+
d.location = 'file:///v.mp4'
|
|
1512
|
+
m.add_document(d)
|
|
1513
|
+
v1 = m.new_view(); v1.metadata.app = 'http://app/producer'
|
|
1514
|
+
v1.new_annotation(AnnotationTypes.TimeFrame, label='bars')
|
|
1515
|
+
d.add_property('frameCount', 51248.0)
|
|
1516
|
+
d.add_property('duration', 1709977.0)
|
|
1517
|
+
|
|
1518
|
+
# downstream app loads it and re-derives a *different* duration value
|
|
1519
|
+
m2 = Mmif(m.serialize())
|
|
1520
|
+
d2 = m2['d1']
|
|
1521
|
+
v2 = m2.new_view(); v2.metadata.app = 'http://app/consumer'
|
|
1522
|
+
v2.new_annotation(AnnotationTypes.TimeFrame, label='caption')
|
|
1523
|
+
d2.add_property('frameCount', 51248) # same value -> de-duped away
|
|
1524
|
+
d2.add_property('duration', 1709975) # different value -> new annotation
|
|
1525
|
+
out = Mmif(m2.serialize()) # must not raise KeyError
|
|
1526
|
+
|
|
1527
|
+
caps = [a for view in out.views
|
|
1528
|
+
for a in view.get_annotations(AnnotationTypes.Annotation)]
|
|
1529
|
+
durations = {a.get_property('duration') for a in caps if 'duration' in a}
|
|
1530
|
+
# both distinct duration assertions coexist; neither is lost or overwritten
|
|
1531
|
+
self.assertEqual(2, len(caps))
|
|
1532
|
+
self.assertEqual({1709977.0, 1709975}, durations)
|
|
1533
|
+
|
|
1534
|
+
def test_factor_out_shared_properties(self):
|
|
1535
|
+
# A view-level metadata property (document/timeUnit/labelset) shared
|
|
1536
|
+
# across all annotations of a type is factored up into the view's
|
|
1537
|
+
# `contains` on serialize, while instance-level properties (label) and
|
|
1538
|
+
# anchors stay on the annotations.
|
|
1539
|
+
m = Mmif(validate=False)
|
|
1540
|
+
v = m.new_view(); v.metadata.app = tester_appname
|
|
1541
|
+
v.new_annotation(AnnotationTypes.TimeFrame, document='m1', timeUnit='ms',
|
|
1542
|
+
start=0, end=5, label='bars')
|
|
1543
|
+
v.new_annotation(AnnotationTypes.TimeFrame, document='m1', timeUnit='ms',
|
|
1544
|
+
start=5, end=9, label='slate')
|
|
1545
|
+
out = json.loads(m.serialize())
|
|
1546
|
+
vout = out['views'][0]
|
|
1547
|
+
tf_key = next(k for k in vout['metadata']['contains'] if 'TimeFrame' in k)
|
|
1548
|
+
contains = vout['metadata']['contains'][tf_key]
|
|
1549
|
+
# shared `document`/`timeUnit` are factored up ...
|
|
1550
|
+
self.assertEqual('m1', contains.get('document'))
|
|
1551
|
+
self.assertEqual('ms', contains.get('timeUnit'))
|
|
1552
|
+
for a in vout['annotations']:
|
|
1553
|
+
# ... and removed from each annotation ...
|
|
1554
|
+
self.assertNotIn('document', a['properties'])
|
|
1555
|
+
self.assertNotIn('timeUnit', a['properties'])
|
|
1556
|
+
# ... while anchors and the differing label stay per-annotation
|
|
1557
|
+
self.assertIn('start', a['properties'])
|
|
1558
|
+
self.assertIn('label', a['properties'])
|
|
1559
|
+
# round-trip: the factored values still resolve per annotation
|
|
1560
|
+
m2 = Mmif(m.serialize())
|
|
1561
|
+
for a in m2.views[0].get_annotations(AnnotationTypes.TimeFrame):
|
|
1562
|
+
self.assertEqual('m1', a.get_property('document'))
|
|
1563
|
+
self.assertEqual('ms', a.get_property('timeUnit'))
|
|
1564
|
+
|
|
1565
|
+
def test_factor_out_protects_listed_props_and_skips_singletons(self):
|
|
1566
|
+
# Blocklisted props (anchors/links) are never factored even when
|
|
1567
|
+
# identical across annotations; a type with a single annotation is
|
|
1568
|
+
# skipped (needs > 1 to be genuinely shared).
|
|
1569
|
+
m = Mmif(validate=False)
|
|
1570
|
+
v = m.new_view(); v.metadata.app = tester_appname
|
|
1571
|
+
tf = v.new_annotation(AnnotationTypes.TimeFrame, start=0, end=5)
|
|
1572
|
+
td1 = v.new_textdocument(text='a'); td2 = v.new_textdocument(text='b')
|
|
1573
|
+
# two Alignments share the same `source` -- must NOT be factored out
|
|
1574
|
+
v.new_annotation(AnnotationTypes.Alignment, source=tf.id, target=td1.id)
|
|
1575
|
+
v.new_annotation(AnnotationTypes.Alignment, source=tf.id, target=td2.id)
|
|
1576
|
+
out = json.loads(m.serialize())
|
|
1577
|
+
aligns = [a for a in out['views'][0]['annotations'] if 'Alignment' in a['@type']]
|
|
1578
|
+
for a in aligns:
|
|
1579
|
+
self.assertIn('source', a['properties'])
|
|
1580
|
+
# a single-annotation type is not factored (threshold)
|
|
1581
|
+
m2 = Mmif(validate=False); v2 = m2.new_view(); v2.metadata.app = tester_appname
|
|
1582
|
+
v2.new_annotation(AnnotationTypes.TimeFrame, document='m1', start=0, end=5)
|
|
1583
|
+
out2 = json.loads(m2.serialize())
|
|
1584
|
+
self.assertIn('document', out2['views'][0]['annotations'][0]['properties'])
|
|
1585
|
+
|
|
1586
|
+
def test_alignment_cached_from_view_level_source_target(self):
|
|
1587
|
+
# regression for the `_cache_alignment` fix: `source`/`target` supplied
|
|
1588
|
+
# as view-level `contains` defaults (distributed to ephemeral, absent
|
|
1589
|
+
# from the annotation's own properties) must still be honored.
|
|
1590
|
+
al = "http://mmif.clams.ai/vocabulary/Alignment/v1"
|
|
1591
|
+
tf = "http://mmif.clams.ai/vocabulary/TimeFrame/v6"
|
|
1592
|
+
td = "http://mmif.clams.ai/vocabulary/TextDocument/v2"
|
|
1593
|
+
raw = {
|
|
1594
|
+
"metadata": {"mmif": "http://mmif.clams.ai/1.2.0"},
|
|
1595
|
+
"documents": [],
|
|
1596
|
+
"views": [{"id": "v1",
|
|
1597
|
+
"metadata": {"app": "http://x/1", "contains": {
|
|
1598
|
+
al: {"source": "v1:tf1", "target": "v1:td1"}}},
|
|
1599
|
+
"annotations": [
|
|
1600
|
+
{"@type": tf, "properties": {"id": "v1:tf1", "start": 0, "end": 5}},
|
|
1601
|
+
{"@type": td, "properties": {"id": "v1:td1", "text": {"@value": "x"}}},
|
|
1602
|
+
{"@type": al, "properties": {"id": "v1:a1"}}]}]
|
|
1603
|
+
}
|
|
1604
|
+
m = Mmif(json.dumps(raw), validate=False)
|
|
1605
|
+
self.assertTrue(list(m['v1:tf1'].get_all_aligned()))
|
|
1606
|
+
|
|
1607
|
+
def test_factor_out_shared_properties_flag(self):
|
|
1608
|
+
# `factor_out_shared_properties` gates factoring independently of
|
|
1609
|
+
# `autogenerate_capital_annotations`.
|
|
1610
|
+
m = Mmif(validate=False)
|
|
1611
|
+
v = m.new_view(); v.metadata.app = tester_appname
|
|
1612
|
+
v.new_annotation(AnnotationTypes.TimeFrame, document='m1', start=0, end=5)
|
|
1613
|
+
v.new_annotation(AnnotationTypes.TimeFrame, document='m1', start=5, end=9)
|
|
1614
|
+
# disabled -> shared `document` stays on each annotation
|
|
1615
|
+
off = json.loads(m.serialize(factor_out_shared_properties=False))
|
|
1616
|
+
for a in off['views'][0]['annotations']:
|
|
1617
|
+
self.assertIn('document', a['properties'])
|
|
1618
|
+
# independent of generation: a reloaded MMIF (props already in
|
|
1619
|
+
# `.properties`) is still factored with capital-annotation generation OFF
|
|
1620
|
+
reloaded = Mmif(m.serialize(factor_out_shared_properties=False))
|
|
1621
|
+
on = json.loads(reloaded.serialize(autogenerate_capital_annotations=False,
|
|
1622
|
+
factor_out_shared_properties=True))
|
|
1623
|
+
tf_key = next(k for k in on['views'][0]['metadata']['contains'] if 'TimeFrame' in k)
|
|
1624
|
+
self.assertEqual('m1', on['views'][0]['metadata']['contains'][tf_key].get('document'))
|
|
1625
|
+
for a in on['views'][0]['annotations']:
|
|
1626
|
+
self.assertNotIn('document', a['properties'])
|
|
1627
|
+
|
|
1455
1628
|
def test_capital_annotation_generation_viewfinder(self):
|
|
1456
1629
|
mmif = Mmif(validate=False)
|
|
1457
1630
|
for i in range(1, 3):
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
{mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/video-transcript-demux-fa.json
RENAMED
|
File without changes
|
|
File without changes
|
{mmif_python-1.5.1 → mmif_python-1.5.3}/tests/mmif-examples/others/video-transcript-demux.json
RENAMED
|
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
|