Skip to content
Snippets Groups Projects
Commit 31e0b872 authored by François  LAURENT's avatar François LAURENT
Browse files

Merge branch 'dev' into main

parents d6492aa1 f664772e
No related branches found
No related tags found
No related merge requests found
Pipeline #100086 passed
name = "TaggingBackends"
uuid = "e551f703-3b82-4335-b341-d497b48d519b"
authors = ["François Laurent", "Institut Pasteur"]
version = "0.11.1"
version = "0.12"
[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
......
[tool.poetry]
name = "TaggingBackends"
version = "0.11.1"
version = "0.12.0"
description = "Backbone for LarvaTagger.jl tagging backends"
authors = ["François Laurent"]
......
......@@ -66,7 +66,9 @@ because label colors are also stored in the `labelspec` attribute.
With taggingbackends==0.9, a related attribute was introduced:
`secondarylabelspec`. To get a unique array of indexable labels, both primary
and secondary labels in a same array, use `full_label_list` instead.
and secondary labels in a same array, use either `encoding_label_list` or
`decoding_label_list` instead. The two properties typically equal, unless
attribute `decodingspec` is defined.
"""
class Labels:
......@@ -84,6 +86,7 @@ class Labels:
self.secondarylabelspec = None
self._tracking = tracking
self._input_labels = None
self.decodingspec = None
#
self.filepath = None # unused attribute; may help to manage data
# dependencies in the future
......@@ -177,8 +180,10 @@ class Labels:
elif len(key) == 3:
run, larva, timestamp = key
self.set_timestep(run, larva, timestamp, value)
elif len(key) < 1:
raise IndexError("too few dimensions")
else:
raise IndexError("too few of many dimensions")
raise IndexError("too many dimensions")
else:
run = key
self.set_run(run, value)
......@@ -264,6 +269,7 @@ class Labels:
self.labels, self.metadata = new_self.labels, new_self.metadata
self.labelspec, self.units = new_self.labelspec, new_self.units
self.secondarylabelspec = new_self.secondarylabelspec
# no `decodingspec` in label files
self.tracking = new_self.tracking
return self
......@@ -306,6 +312,7 @@ class Labels:
self.units = data.get("units", {})
self.labelspec = data.get("labels", {})
self.secondarylabelspec = data.get("secondarylabels", [])
self.decodingspec = None # ensure it is not set
self._tracking = data.get("dependencies", [])
if isinstance(self._tracking, dict):
self._tracking = [self._tracking]
......@@ -322,7 +329,7 @@ class Labels:
List of str: all different labels including primary and secondary labels.
"""
@property
def full_label_list(self):
def encoding_label_list(self):
if isinstance(self.labelspec, dict):
labelset = self.labelspec['names']
else:
......@@ -331,6 +338,18 @@ class Labels:
labelset = labelset + self.secondarylabelspec
return labelset
"""
List of str: all different labels including primary and secondary labels.
"""
@property
def decoding_label_list(self):
labelspec = self.decodingspec
if labelspec is None:
return self.encoding_label_list
if self.secondarylabelspec:
labelset = labelset + self.secondarylabelspec
return labelset
"""
Encode the text labels as indices (`int` or `list` of `int`).
......@@ -345,7 +364,7 @@ class Labels:
elif isinstance(label, dict):
encoded = {t: self.encode(l) for t, l in label.items()}
else:
labelset = self.full_label_list
labelset = self.encoding_label_list
if isinstance(label, str):
encoded = labelset.index(label) + 1
elif isinstance(label, int):
......@@ -358,7 +377,25 @@ class Labels:
"""
Decode the label indices as text (`str` or `list` of `str`).
Text labels are picked in `labelspec`.
Text labels are picked in `labelspec`, or `decodingspec` instead if defined.
`decodingspec` is set to decode the encoded output from a tagger that
defines redundant labels, which are remapped onto labels in `labelspec`.
These may include several sections in their config file, including
`original_behavior_labels`, `behavior_labels`, `remapped_behavior_labels`.
Note that `decodingspec` should not be set to decode a label file. A label
file should use and mention the remapped labels only, if the latter are
defined by the tagger used to generate the label file. In the case of
decoding a label file, `labelspec` only should be defined in the `Labels`
object.
To forget about these considerations, call `load_model_config` first if
processing the output of a tagger, or do not call that method otherwise.
MaggotUBA's `predict_model` does not call `decode` but directly indexes into
`behavior_labels`. As a consequence, the above subtleties do not apply to
MaggotUBA.
"""
def decode(self, label=None):
if label is None:
......@@ -368,7 +405,7 @@ class Labels:
elif isinstance(label, dict):
decoded = {t: self.decode(l) for t, l in label.items()}
else:
labelset = self.full_label_list
labelset = self.decoding_label_list
if isinstance(label, int):
decoded = labelset[label-1]
elif isinstance(label, str):
......@@ -378,6 +415,14 @@ class Labels:
decoded = [labelset[l-1] for l in label]
return decoded
def load_model_config(self, config):
try:
self.labelspec = config["remapped_behavior_labels"]
except KeyError:
self.labelspec = config["behavior_labels"]
else:
self.decodingspec = config["behavior_labels"]
class LabelEncoder(json.JSONEncoder):
def default(self, labels):
if isinstance(labels, Labels):
......
......@@ -14,6 +14,7 @@ Usage: tagging-backend [train|predict] --model-instance <name>
tagging-backend train ... --skip-make-dataset --skip-build-features
tagging-backend predict ... --make-dataset --build-features
tagging-backend predict ... --sandbox <token>
tagging-backend --help
`tagging-backend` typically is run using `poetry run`.
A name must be provided to identify the trained model and its location within
......@@ -83,9 +84,9 @@ def main(fun=None):
format="%(levelname)s:%(name)s: %(message)s")
if fun is None:
# called by scripts/tagging-backend
if not sys.argv[1:]:
if not sys.argv[1:] or any(arg == '--help' for arg in sys.argv):
help(True)
sys.exit("too few input arguments; subcommand expected: 'train' or 'predict'")
#sys.exit("too few input arguments; subcommand expected: 'train' or 'predict'")
return
train_or_predict = sys.argv[1]
project_dir = model_instance = None
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment