Skip to main content

Helper for converting CONLLU files and uploading the corpus to LiRI Corpus Platform (LCP)

Project description

LCP CLI module

Command-line tool for converting CONLLU files and uploading the corpus to LCP

Installation

Make sure you have python 3.11+ with pip installed in your local environment, then run

pip install lcpcli

Usage

Example:

lcpcli -i ~/conll_ext/ -o ~/upload/ -m upload -k $API_KEY -s $API_SECRET -p my_project

Help:

lcpcli --help

lcpcli takes a corpus of CoNLL-U (PLUS) files and imports it to a project created in an LCP instance, such as catchphrase.

Besides the standard token-level CoNLL-U fields (form, lemma, upos, xpos, feats, head, deprel, deps) one can also provide document- and sentence-level annotations using comment lines in the files (see the CoNLL-U Format section)

A more advanced functionality, lcpcli supports annotations aligned at the character level, such as named entities. See the Named Entities section for more information

Example corpus

lcpcli ships with an example one-video "corpus": the video is an excerpt from the CC-BY 3.0 "Big Buck Bunny" video ((c) copyright 2008, Blender Foundation / www.bigbuckbunny.org) and the "transcription" is a sample of the Declaration of the Human Rights

To populate a folder with the example data, use this command

lcpcli --example /destination/folder/

This will create a subfolder named free_video_corpus in /destination/folder which, itself, contains two subfolders: input and output. The input subfolder contains four files:

  • doc.conllu is a CoNLL-U Plus file that contains the textual data, with time alignments in seconds at the token- (start and end in the MISC column), segment- (# start = and # end = comments) and document-level (#newdoc start = and #newdoc end =)
  • namedentity.tsv is a tab-separated value lookup file that contains information about the named entities, where each row associates an ID reported in the namedentity token cells of doc.conllu with two attributes, type and form
  • shot.tsv is a tab-separated value file that defines time-aligned annotations about the shots in the video in the view column, where the start and end columns are timestamps, in seconds, relative to the document referenced in the doc_id column
  • meta.json is a JSON file that defines the structure of the corpus, used both for pre-processing the data before upload, and when adding the data to the LCP database. Read on for information on the definitions in this file

CoNLL-U Format

The CoNLL-U format is documented at: https://universaldependencies.org/format.html

The LCP CLI converter will treat all the comments that start with # newdoc KEY = VALUE as document-level attributes. This means that if a CoNLL-U file contains the line # newdoc author = Jane Doe, then in LCP all the sentences from this file will be associated with a document whose meta attribute will contain author: 'Jane Doe'

All other comment lines following the format # key = value will add an entry to the meta attribute of the segment corresponding to the sentence below that line (ie not at the document level)

The key-value pairs in the MISC column of a token line will go in the meta attribute of the corresponding token, with the exceptions of these key-value combinations:

  • SpaceAfter=Yes vs. SpaceAfter=No (case senstive) controls whether the token will be represented with a trailing space character in the database
  • start=n.m|end=o.p (case senstive) will align tokens, segments (sentences) and documents along a temporal axis, where n.m and o.p should be floating values in seconds

See below how to report all the attributes in the template .json file

CoNLL-U Plus

CoNLL-U Plus is an extension to the CoNLLU-U format documented at: https://universaldependencies.org/ext-format.html

If your files start with a comment line of the form # global.columns = ID FORM LEMMA UPOS XPOS FEATS HEAD DEPREL DEPS MISC, lcpcli will treat them as CoNLL-U Plus files and process the columns according to the names you set in that line

Annotations of sequences of tokens (e.g. Named Entities)

You can use lcpcli to define annotations on sequences of tokens below the segment level, for example named entities. To do so, you will need to prepare your corpus as CoNLL-U Plus files which must define a dedicated column, e.g. namedentity:

# global.columns = ID FORM LEMMA UPOS XPOS FEATS HEAD DEPREL DEPS MISC namedentity

All the tokens belonging to the same named entity should report the same index in that column, or _ (as per CoNLL-U conventions) if it doesn't belong to a named entity. For example:

1	Adopted	adopt	VERB	V	Tense=Past|VerbForm=Part	0	root	_	start=2.20|end=2.30	_
2	and	and	CCONJ	CC	_	3	cc	_	start=2.30|end=2.35	_
3	proclaimed	proclaim	VERB	V	Tense=Past|VerbForm=Part	1	conj	_	start=2.38|end=2.45	_
4	by	by	ADP	E	_	7	case	_	start=2.45|end=2.50	_
5	General	general	ADJ	A	Degree=Pos	6	amod	_	start=2.55|end=2.75	1
6	Assembly	assembly	NOUN	S	Number=Sing	7	nmod	_	start=2.85|end=3.15	1
7	resolution	resolution	NOUN	S	Number=Sing	3	obl	_	start=3.20|end=3.22	_
8	217	217	NUM	N	NumType=Card	7	nummod	_	SpaceAfter=No|start=3.24|end=3.40	_
9	A	A	X	X	_	8	dep	_	start=3.42|end=3.44	_
10	(	(	PUNCT	FB	_	11	punct	_	SpaceAfter=No|start=3.44|end=3.45	_
11	III	third	ADJ	NO	Degree=Pos|NumType=Ord	8	amod	_	SpaceAfter=No|start=3.47|end=3.53	_
12	)	)	PUNCT	FB	_	11	punct	_	start=3.53|end=3.54	_
13	of	of	ADP	E	_	14	case	_	start=3.55|end=3.62	_
14	10	10	NUM	N	NumType=Card	7	nmod	_	start=3.75|end=4.07	2
15	December	December	PROPN	SP	_	14	flat	_	start=4.09|end=4.13	2
16	1948	1948	NUM	N	NumType=Card	14	flat	_	SpaceAfter=No|start=4.15|end=4.23	2
17	.	.	PUNCT	FS	_	1	punct	_	start=4.24|end=4.25	_

In this example, tokens 5-6 belong to the same named entity ("General Assembly") and "10 December 1948" forms another named entity.

The directory containing your corpus files should also include one TSV file named after that column: the filename should match the column name, all in lower-case, plus an extension (e.g. .tsv) -- in the example corpus, the column as reported in the first comment line (global.columns) is named namedentity and, correspondingly, the TSV file is named namedentity.tsv. Its first line should report headers, starting with namedentity_id and then any attributes associated with a named entity. The value in the first cell of all the non-header lines should correspond to the ones listed in the CoNLL-U file(s) for lookup purposes. For example:

namedentity_id	type	form
1	ORG	General Assembly
2	DATE	10 December 1948

When parsed along with the CoNLL-U Plus lines above, this would associate the corresponding occurrence of the sequence "General Assembly" with a named entity of type ORG and the corresponding occurrence of "10 December 1948" with a named entity of type DATE.

Finally, you need to report a corresponding entity type in the template .json under the layer key, for example:

"NamedEntity": {
    "abstract": false,
    "layerType": "span",
    "contains": "Token",
    "attributes": {
        "form": {
            "isGlobal": false,
            "type": "text",
            "nullable": false
        },
        "type": {
            "isGlobal": false,
            "type": "categorical",
            "nullable": true
        }
    }
},

Make sure to set the abstract, layerType and contains attributes as illustrated above. See the section Convert and Upload for a full example of a template .json file.

One can then query named entities by specifying that they are contained in segments, and that they should contain specific tokens. For example, the following DQD query would match all the named entities the corpus' segments that contain an adjective token:

Segment s

NamedEntity@s ne
    type = "ORG"

Token@ne t
    upos = "ADJ"

res => plain
    context
        s
    entities
        ne

Annotations of sequences of segments (e.g. Topics)

You can use lcpcli to define annotations on sequences of segments below the document level, for example topics. The approach is almost identical to the one for annotations of sequences of tokens; the following only describes the differences:

  • one does not define a new column in global.columns
  • one does not report the lookup indices in the token lines
  • one reports the indices as segment-level comments, named to match the TSV file; for example, a segment-level comment # topic = 1 will look up the file topic.tsv for a row whose first cell has the value 1

Just like with token-level annotations, all consecutive segments sharing the same value in the annotation comment will be grouped together as one occurrence of that annotation.

One can then query segments that belong to specific topics. For example the following DQD query would match all the segments that belong to a topic named "bunny" (assuming topic.csv has a corresponding column name):

Topic top
    name = "bunny"

Segment@top s

res => plain
    context
        s
    entities
        s

Time-aligned annotations

Your corpus can also include annotations that do not strictly group entities together. The example video corpus includes an annotation named shot that is time-aligned but does not necessarily align with tokens or segments on the time axis (e.g. a shot can start in the middle of a sentence and end some time after its end)

Much like with the annotation types described above, you should also include a corresponding TSV file. The first column should list unique IDs; one column should be named doc_id and report the ID of the corresponding document (make sure to include corresponding # newdoc id = <ID> comments in your CoNLL-U files); two columns named start and end should list the time points for temporal anchoring, measured in seconds from the start of the document's media file; with extra columns for additional attributes. For example, shot.tsv starts with:

shot_id	doc_id	start	end	view
1	Bunny	0.00	8.00	wide angle
2	Bunny	8.05	12.50	low angle
3	Bunny	12.75	16.00	face-cam

Your template .json file should report Shot under layer, for example:

"Shot": {
    "abstract": false,
    "layerType": "unit",
    "anchoring": {
        "location": false,
        "stream": false,
        "time": true
    },
    "attributes": {
        "view": {
            "type": "categorical"
        }
    }
},

Assuming the sentences are also time-aligned (as in the example corpus) you can then query segments that overlap with specific shots, for example:

Segment s

Shot sh
    OR # either...
        AND # ... the shot start in the middle of the segment
            start >= s.start + 0.0s
            start <= s.end + 0.0s
        AND # ... or the short ends in the middle of the segment
            end >= s.start + 0.0s
            end <= s.end + 0.0s

res => plain
    context
        s
    entities
        ne

Global attributes

In some cases, it makes sense for multiple entity types to share references: in those cases, they can define global attributes. An example of a global attribute is a speaker or an agent that can have a name, an age, etc. and be associated with both a segment (a sentence) and, say, a gesture. The corpus template would include definitions along these lines:

"globalAttributes": {
    "agent": {
        "type": "dict",
        "keys": {
            "name": {
                "type": "text"
            },
            "age": {
                "type": "number"
            }
        }
    }
},
"layer": {
    "Segment": {
        "abstract": false,
        "layerType": "span",
        "contains": "Token",
        "attributes": {
            "agent": {
                "ref": "agent"
            }
        }
    },
    "Gesture": {
        "abstract": false,
        "layerType": "unit",
        "anchoring": {
            "time": true
        },
        "attributes": {
            "agent": {
                "ref": "agent"
            }
        }
    }
}

You should include a file named global_attribute_agent.tsv (mind the singular on attribute) with three columns: agent_id, name and age, and reference the values of agent_id appropriately as a sentence-level comment in your CoNLL-U files as well as in a file named gesture.tsv. For example:

global_attribute_agent.tsv:

agent_id	agent
10	{"name": "Jane Doe", "age": 37}

CoNLL-U file:

# newdoc id = video1

# sent_id = 1
# agent_id = 10
The the _ _ _

gesture.tsv:

gesture_id	agent_id	doc_id	start	end
1	10	video1	1.25	2.6

Media files

If your corpus includes media files, your .json template should report it under a mediaSlots key in meta, e.g.:

"meta": {
    "name": "Free Single-Video Corpus",
    "author": "LiRI",
    "date": "2024-06-13",
    "version": 1,
    "corpusDescription": "Single, open-source video with annotated shots and a placeholder text stream from the Universal Declaration of Human Rights annotated with named entities",
    "mediaSlots": {
        "video": {
            "mediaType": "video",
            "isOptional": false
        }
    }
},

Your CoNLL-U file(s) should accordingly report each document's media file's name in a comment, like so:

# newdoc video = bunny.mp4

The .json template should also define a main key named tracks to control what annotations will be represented along the time axis. For example the following will report shot, segment and named entities in a timeline:

"tracks": {
    "layers": {
        "Shot": {},
        "Segment": {},
        "NamedEntity": {}
    }
}

Finally, your output corpus folder should include a subfolder named media in which all the referenced media files have been placed

Convert and Upload

  1. Create a directory in which you have all your properly-fromatted CONLLU files

  2. In the same directory, create a template .json file that describes your corpus structure (see above about the attributes key on Document and Segment), for example:

{
    "meta": {
        "name": "Free Single-Video Corpus",
        "author": "LiRI",
        "date": "2024-06-13",
        "version": 1,
        "corpusDescription": "Single, open-source video with annotated shots and a placeholder text stream from the Universal Declaration of Human Rights annotated with named entities",
        "mediaSlots": {
            "video": {
                "mediaType": "video",
                "isOptional": false
            }
        }
    },
    "firstClass": {
        "document": "Document",
        "segment": "Segment",
        "token": "Token"
    },
    "layer": {
        "Token": {
            "abstract": false,
            "layerType": "unit",
            "anchoring": {
                "location": false,
                "stream": true,
                "time": true
            },
            "attributes": {
                "form": {
                    "isGlobal": false,
                    "type": "text",
                    "nullable": true
                },
                "lemma": {
                    "isGlobal": false,
                    "type": "text",
                    "nullable": false
                },
                "upos": {
                    "isGlobal": true,
                    "type": "categorical",
                    "nullable": true
                },
                "xpos": {
                    "isGlobal": false,
                    "type": "categorical",
                    "nullable": true
                },
                "ufeat": {
                    "isGlobal": false,
                    "type": "dict",
                    "nullable": true
                }
            }
        },
        "DepRel": {
            "abstract": true,
            "layerType": "relation",
            "attributes": {
                "udep": {
                    "type": "categorical",
                    "isGlobal": true,
                    "nullable": false
                },
                "source": {
                    "name": "dependent",
                    "entity": "Token",
                    "nullable": false
                },
                "target": {
                    "name": "head",
                    "entity": "Token",
                    "nullable": true
                },
                "left_anchor": {
                    "type": "number",
                    "nullable": false
                },
                "right_anchor": {
                    "type": "number",
                    "nullable": false
                }
            }
        },
        "NamedEntity": {
            "abstract": false,
            "layerType": "span",
            "contains": "Token",
            "attributes": {
                "form": {
                    "isGlobal": false,
                    "type": "text",
                    "nullable": false
                },
                "type": {
                    "isGlobal": false,
                    "type": "categorical",
                    "nullable": true
                }
            }
        },
        "Shot": {
            "abstract": false,
            "layerType": "span",
            "anchoring": {
                "location": false,
                "stream": false,
                "time": true
            },
            "attributes": {
                "view": {
                    "isGlobal": false,
                    "type": "categorical",
                    "nullable": false
                }
            }
        },
        "Segment": {
            "abstract": false,
            "layerType": "span",
            "contains": "Token",
            "attributes": {
                "meta": {
                    "text": {
                        "type": "text"
                    },
                    "start": {
                        "type": "text"
                    },
                    "end": {
                        "type": "text"
                    }
                }
            }
        },
        "Document": {
            "abstract": false,
            "contains": "Segment",
            "layerType": "span",
            "attributes": {
                "meta": {
                    "audio": {
                        "type": "text",
                        "isOptional": true
                    },
                    "video": {
                        "type": "text",
                        "isOptional": true
                    },
                    "start": {
                        "type": "number"
                    },
                    "end": {
                        "type": "number"
                    },
                    "name": {
                        "type": "text"
                    }
                }
            }
        }
    },
    "tracks": {
        "layers": {
            "Shot": {},
            "Segment": {},
            "NamedEntity": {}
        }
    }
}
  1. If your corpus defines a character-anchored entity type such as named entities, make sure you also include a properly named and formatted TSV file for it in the directory (see the Named Entities section)

  2. Visit an LCP instance (e.g. catchphrase) and create a new project if you don't already have one where your corpus should go

  3. Retrieve the API key and secret for your project by clicking on the button that says: "Create API Key"

    The secret will appear at the bottom of the page and remain visible only for 120s, after which it will disappear forever (you would then need to revoke the API key and create a new one)

    The key itself is listed above the button that says "Revoke API key" (make sure to not copy the line that starts with "Secret Key" along with the API key itself)

  4. Once you have your API key and secret, you can start converting and uploading your corpus by running the following command:

lcpcli -i $CONLLU_FOLDER -o $OUTPUT_FOLDER -m upload -k $API_KEY -s $API_SECRET -p $PROJECT_NAME --live
  • $CONLLU_FOLDER should point to the folder that contains your CONLLU files
  • $OUTPUT_FOLDER should point to another folder that will be used to store the converted files to be uploaded
  • $API_KEY is the key you copied from your project on LCP (still visible when you visit the page)
  • $API_SECRET is the secret you copied from your project on LCP (only visible upon API Key creation)
  • $PROJECT_NAME is the name of the project exactly as displayed on LCP -- it is case-sensitive, and space characters should be escaped

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lcpcli-0.1.6.tar.gz (10.6 MB view details)

Uploaded Source

Built Distribution

lcpcli-0.1.6-py3-none-any.whl (10.6 MB view details)

Uploaded Python 3

File details

Details for the file lcpcli-0.1.6.tar.gz.

File metadata

  • Download URL: lcpcli-0.1.6.tar.gz
  • Upload date:
  • Size: 10.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.0 CPython/3.11.3

File hashes

Hashes for lcpcli-0.1.6.tar.gz
Algorithm Hash digest
SHA256 c1a892e8affa1e0cab033ca7facd73fc82f4ba95a72c76bc3e46f48299fc4ea5
MD5 e7e199638444fe2c867c4fc11fb8e4f2
BLAKE2b-256 d45bd26c517ad8746b85cef0d4f7167e5ca18ede3a7d9156577deb621b1cc9fa

See more details on using hashes here.

File details

Details for the file lcpcli-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: lcpcli-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 10.6 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.0 CPython/3.11.3

File hashes

Hashes for lcpcli-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 d20cbf25d768b03b02b9c29677b9b162c2ef5084685a58879cd28a6062758b45
MD5 3aab989708cceb3e4ff8a87709d3ffc9
BLAKE2b-256 b3a4ddfcfad63297db7370fe09a4cda62f42b391632f3e7e822af16e84d1b507

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page