Repository: voxel51/fiftyone
Pull request: Provide custom task name for CVAT
## What changes are proposed in this pull request?

Closes #1753 

1. Custom task name can be passed for labelling data in CVAT such as `dataset.annotate("anno_key", backend="cvat", task_name="Custom task name", ...)`
2. Default task name for CVAT to include an annotation key such as `FiftyOne_{dataset_name}_{annotation_key}`

## How is this patch tested? If it is not, please explain why.

added few lines to existing cvat unit-tests.

## Release Notes

### Is this a user-facing change that should be mentioned in the release notes?

<!--
Please fill in relevant options below with an "x", or by clicking the checkboxes
after submitting this pull request. Example:
-   [x] Selected option
-->

-   [ ] No. You can skip the rest of this section.
-   [x] Yes. Give a description of this change to be included in the release
        notes for FiftyOne users.

A custom task name can be provided with CVAT annotation backend. Default CVAT task name now includes an annotation key.

### What areas of FiftyOne does this PR affect?

-   [ ] App: FiftyOne application changes
-   [ ] Build: Build and test infrastructure changes
-   [x] Core: Core `fiftyone` Python library changes
-   [x] Documentation: FiftyOne documentation changes
-   [ ] Other


Problem statement:
[FR] Allow task names to be provided when annotating with CVAT
Currently, when annotating with the CVAT integration, the name for the tasks that get generated are hardcoded as `FiftyOne_{dataset_name}` which is not ideal when launching multiple annotation runs on the same dataset. There should be an argument allowing the user to provide one or more task names to use for the generated tasks.

Also, changing the default to `FiftyOne_{dataset_name}_{annotation_key}` would be helpful as well.

Hints from issue discussion:

Commit message: provide custom task name when uploading to cvat

Patch to review:
diff --git a/fiftyone/utils/cvat.py b/fiftyone/utils/cvat.py
index 4c33cfc741a..a0986607094 100644
--- a/fiftyone/utils/cvat.py
+++ b/fiftyone/utils/cvat.py
@@ -3060,6 +3060,7 @@ class CVATBackendConfig(foua.AnnotationBackendConfig):
             default, no project is used
         project_id (None): an optional ID of an existing CVAT project to which
             to upload the annotation tasks. By default, no project is used
+        task_name (None): an optional task name to use for the created CVAT task
         occluded_attr (None): an optional attribute name containing existing
             occluded values and/or in which to store downloaded occluded values
             for all objects in the annotation run
@@ -3091,6 +3092,7 @@ def __init__(
         job_reviewers=None,
         project_name=None,
         project_id=None,
+        task_name=None,
         occluded_attr=None,
         group_id_attr=None,
         issue_tracker=None,
@@ -3109,6 +3111,7 @@ def __init__(
         self.job_reviewers = job_reviewers
         self.project_name = project_name
         self.project_id = project_id
+        self.task_name = task_name
         self.occluded_attr = occluded_attr
         self.group_id_attr = group_id_attr
         self.issue_tracker = issue_tracker
@@ -4290,8 +4293,11 @@ def upload_samples(self, samples, backend):
                     project_id = self.create_project(project_name, cvat_schema)
                     project_ids.append(project_id)
 
-                _dataset_name = samples_batch._dataset.name.replace(" ", "_")
-                task_name = "FiftyOne_%s" % _dataset_name
+                if config.task_name is None:
+                    _dataset_name = samples_batch._dataset.name.replace(" ", "_")
+                    task_name = "FiftyOne_%s" % _dataset_name
+                else:
+                    task_name = config.task_name
 
                 (
                     task_id,
diff --git a/tests/intensive/cvat_tests.py b/tests/intensive/cvat_tests.py
index 07766210f81..a6738f56876 100644
--- a/tests/intensive/cvat_tests.py
+++ b/tests/intensive/cvat_tests.py
@@ -417,6 +417,7 @@ def test_task_creation_arguments(self):
 
         anno_key = "anno_key"
         bug_tracker = "test_tracker"
+        task_name = "test_task"
         results = dataset.annotate(
             anno_key,
             backend="cvat",
@@ -427,6 +428,7 @@ def test_task_creation_arguments(self):
             job_assignees=users,
             job_reviewers=users,
             issue_tracker=bug_tracker,
+            task_name=task_name,
         )
         task_ids = results.task_ids
         with results:
@@ -436,6 +438,7 @@ def test_task_creation_arguments(self):
                 task_json = api.get(api.task_url(task_id)).json()
                 self.assertEqual(task_json["bug_tracker"], bug_tracker)
                 self.assertEqual(task_json["segment_size"], 1)
+                self.assertEqual(task_json["name"], task_name)
                 if user is not None:
                     self.assertEqual(task_json["assignee"]["username"], user)
                 for job in api.get(api.jobs_url(task_id)).json():


Code review comments:
diff_hunk: @@ -4290,8 +4293,18 @@ def upload_samples(self, samples, backend):
                     project_id = self.create_project(project_name, cvat_schema)
                     project_ids.append(project_id)
 
-                _dataset_name = samples_batch._dataset.name.replace(" ", "_")
-                task_name = "FiftyOne_%s" % _dataset_name
+                # use custom task name if provided, else use default name
+                if config.task_name is None:
+                    _dataset_name = samples_batch._dataset.name.replace(
+                        " ", "_"
+                    )
+                    latest_anno_key = _get_latest_anno_key(samples)
path: fiftyone/utils/cvat.py
text: @user1:
This is a clever workaround to get the `anno_key`, but now that `config.task_name` is supported, I think it would be best to just rely on the user putting their `anno_key` in the task name if that's what they want.

@user1:
It is unfortunate that we didn't include `anno_key` in either the `upload_annotations()` method or in the `AnnotationBackendConfig`, because it's definitely reasonable to want to use it to generate default values like this 🤦 

@author:
I guess it's enough for me that I can provide a custom task name to differentiate between tasks for the same dataset. 

Do you think that will require a lot of/breaking changes to pass `anno_key` to `upload_annotations`?