Repository: voxel51/fiftyone

Issue:
[BUG] The attributes of CVAT tags don't get exported for image dataset
### System information

-   **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Linux Ubuntu 21.04  
-   **FiftyOne installed from (pip or source)**: FiftyOne installed from pip  
-   **FiftyOne version (run `fiftyone --version`)**: FiftyOne v0.15.0.1, Voxel51, Inc.
-   **Python version**: 3.8.12  

### Commands to reproduce

```
import fiftyone as fo
import fiftyone.utils.cvat as fouc

dataset = fo.Dataset()
fouc.import_annotations(
        dataset,
        task_ids=[cvat_task_id],
        download_media=False,
        data_path=path_to_images_being_annotated
    )
dataset.export(export_dir="./output/",
                       dataset_type=fo.types.CVATImageDataset)
```
Replace cvat_task_id with your CVAT task's id.    
Replace path_to_images_being_annotated with the folder containing the images being annotated at the task id.  

### Describe the problem
Hello

When exporting an image dataset to CVAT format, the tags (classification annotations) are correctly exported, however the attributes of the tags are missing. For all other annotation types (bounding boxes, polygons etc...) the attributes seem to be correctly exported.

The tag's attributes do seem to be present after downloading from a CVAT task, so it's likely the issue comes from the export to CVAT format.

### How to reproduce

Download an image CVAT task with tags that have attributes, then export it. The tags' attributes should be missing in the final xml file.

### Other info / logs
Full log:
 100% |███████████████████████████████████████████████████████████████████████████████| 40/40 [13.4ms elapsed, 0s remaining, 3.0K samples/s]      
Downloading labels from CVAT...
Download complete
Loading labels for field 'classifications'...
 100% |█████████████████████████████████████████████████████████████████████████████████| 2/2 [1.7ms elapsed, 0s remaining, 1.2K samples/s] 
Loading labels for field 'detections'...
 100% |█████████████████████████████████████████████████████████████████████████████████| 2/2 [1.7ms elapsed, 0s remaining, 1.2K samples/s] 
Loading labels for field 'keypoints'...
 100% |█████████████████████████████████████████████████████████████████████████████████| 1/1 [1.2ms elapsed, 0s remaining, 862.7 samples/s] 
 100% |███████████████████████████████████████████████████████████████████████████████| 40/40 [9.3ms elapsed, 0s remaining, 4.3K samples/s] 
 100% |███████████████████████████████████████████████████████████████████████████████| 40/40 [469.2ms elapsed, 0s remaining, 85.2 samples/s] 

### What areas of FiftyOne does this bug affect?

-   [ ] `App`: FiftyOne application issue
-   [x] `Core`: Core `fiftyone` Python library issue
-   [ ] `Server`: Fiftyone server issue

### Willingness to contribute

The FiftyOne Community encourages bug fix contributions. Would you or another
member of your organization be willing to contribute a fix for this bug to the
FiftyOne codebase?

-   [ ] Yes. I can contribute a fix for this bug independently.
-   [ ] Yes. I would be willing to contribute a fix for this bug with guidance
        from the FiftyOne community.
-   [x] No. I cannot contribute a bug fix at this time.



Discussion:

Resolving patch:
diff --git a/fiftyone/utils/cvat.py b/fiftyone/utils/cvat.py
--- a/fiftyone/utils/cvat.py
+++ b/fiftyone/utils/cvat.py
@@ -1623,11 +1623,12 @@ class CVATImageTag(CVATImageAnno):
 
     Args:
         label: the tag string
+        attributes (None): a list of :class:`CVATAttribute` instances
     """
 
-    def __init__(self, label):
+    def __init__(self, label, attributes=None):
         self.label = label
-        CVATImageAnno.__init__(self)
+        CVATImageAnno.__init__(self, attributes=attributes)
 
     def to_classification(self):
         """Returns a :class:`fiftyone.core.labels.Classification`
@@ -1636,7 +1637,8 @@ def to_classification(self):
         Returns:
             a :class:`fiftyone.core.labels.Classification`
         """
-        return fol.Classification(label=self.label)
+        attributes = self._to_attributes()
+        return fol.Classification(label=self.label, **attributes)
 
     @classmethod
     def from_classification(cls, classification):
@@ -1650,7 +1652,9 @@ def from_classification(cls, classification):
             a :class:`CVATImageTag`
         """
         label = classification.label
-        return cls(label)
+
+        _, attributes = cls._parse_attributes(classification)
+        return cls(label, attributes=attributes)
 
     @classmethod
     def from_tag_dict(cls, d):
@@ -1664,7 +1668,9 @@ def from_tag_dict(cls, d):
             a :class:`CVATImageTag`
         """
         label = d["@label"]
-        return cls(label)
+
+        _, attributes = cls._parse_anno_dict(d)
+        return cls(label, attributes=attributes)
 
 
 class CVATImageBox(CVATImageAnno):


Test patch: