A hackable Emacs based data-tagging framework.
You will need Emacs (not tested on version below 26). Recommended way for
installing Emacs Lisp dependencies is to install cask and do cask install
here.
Once done, open cask emacs -Q ./init.sample.el
to check out sample workflows
with few of the existing taggers in ./taggers
directory.
Additionally, for working with audio, we depend on the following command line tools:
mplayer
for playing audios. This can be changed by changingtog-player-command
variable.arecord
andsox
for recording audio. These are only needed if your tagger usestog-input-audio
functions.
Working with and creating a tog tagger needs the user to define:
- Data loader for loading and saving data
- A
tog-item
class and few of it’s methods
A loader takes care of keeping and navigating around the data items being tagged or to be tagged. It also defines mechanism for saving and loading tags.
At the moment, we have a simple JSON based loader (check out tog-io-json-loader
)
which assumes the data items are kept as list of dicts (with mandatory id
keys)
in a JSON file. It dumps tags in a sibling file (adding a .tog
to the source
file name) as a dictionary mapping item id
to tag.
There are plans for supporting better loaders and serialization formats. A
closer to completion one is where we stream data bidirectionally for active
learning. These will be implemented by extending the classes in tog-io
package.
An item to tag is defined by creating a class that inherits from tog-item
class.
The default slots there are id
, which uniquely identifies the item, and tag
which keeps certain representation of tag for the item, or nil
if this item is
untagged as of now.
Let’s assume we are tagging intent (multiple intents are allowed) for a given text. The list of possible intents is the following:
greeting
goodbye
Our item can look like this:
(defclass tog-text-item (tog-item)
((text :initarg :text)))
Also let’s setup the list of intents somewhere
(defcustom tog-text-intents '("greeting" "goodbye") "Example intents")
Next, the following functions/methods need to be defined:
A factory function creates an object of the above class based on an input data structure decided by the data loader.
For a simple JSON loader, the factory function takes a hash-table and returns the newly created object. For this example, we assume that our data is kept like this in a json:
cat ./test/resources/text-intent-data.json
[ {"id": 1, "text": "hello world"}, {"id": 2, "text": "goodbye"} ]
Since the JSON loader reads a list of dictionaries from file as Emacs Lisp hashtables, our factory function can be the following:
(defun make-tog-text-item (table)
(tog-text-item :id (gethash "id" table)
:text (gethash "text" table)))
This adds a tag to an item. Here is an example for the current intent tagging case. Assuming that the tag is just a string and each item can get multiple such tags, we can do something like this:
(cl-defmethod tog-add-tag ((obj tog-text-item) tag)
;; You might want to handle duplicates here
(oset obj :tag (cons tag (oref obj :tag))))
This defines how the item is going to be rendered in the tog-mode
buffer. Since
tog-mode
inherits from org-mode
, you can use various org mode functions. For our
simple case, we will just show the item id
, text
and current tag
in separate
lines.
;; Note that we are provided a clean buffer with tog mode on
(cl-defmethod tog-render ((obj tog-text-item))
(insert "id : " (number-to-string (oref obj :id)) "\n")
(insert "text: " (oref obj :text) "\n")
;; Also showing the currently applied tags
(insert "tags: " (string-join (oref obj :tag) ", ")))
This method defines what happens when we start annotating the currently displayed item. In out case, we will just ask for an intent from the user and add to the current item:
(cl-defmethod tog-annotate ((obj tog-text-item))
(let ((intent (tog-input-choice tog-text-intents)))
(tog-add-tag obj intent)))
Now we can create a loader from our data file and start tagging:
(setq tog-loader (make-tog-io-json-loader "./test/resources/text-intent-data.json" #'make-tog-text-item))
(tog)
After (wrong) tagging, the tags are saved here:
cat ./test/resources/text-intent-data.json.tog
{"2":["goodbye","greeting"],"1":["greeting"]}
tog-nav-hook
is called whenever we navigate to any item. This can be useful for setting up things like auto key presses for tagging speed up.tog-annotate-hook
is called after every annotation act.
Important general commands are listed and bound to sensible defaults in
./init.sample.el
. Check the file for more details.