Repository: voxel51/fiftyone

Issue:
[BUG] Importing VOCDetectionDataset from disk tries to process non-XML files resulting in an exception that halts the import
### System information

-   **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: MacOS 10.15.7
-   **FiftyOne installed from (pip or source)**: pip
-   **FiftyOne version (run `fiftyone --version`)**: FiftyOne v0.15.1, Voxel51, Inc.
-   **Python version**: Python 3.6.8

### Commands to reproduce

As thoroughly as possible, please provide the Python and/or shell commands used
to encounter the issue. Application steps can be described in the next section.

```
import fiftyone as fo
dataset_path = '.../images/trailcam/scraped_jan_2022/scraped'
dataset_name = 'scraped_jan_2022'
dataset = fo.Dataset.from_dir(dataset_type=fo.types.VOCDetectionDataset, data_path=dataset_path, labels_path=dataset_path, name=dataset_name)
```

### Describe the problem

I had a `README.md` file in the directory pointed to by `dataset_path`. This caused the import to raise an exception part way through the import.
I expected fiftyone to be resilient to non-XML files when importing VOC datasets.
Note that I also have Yolov5 label files (.txt files) in the same directory alongside my Pascal VOC XML label files. So even after I moved README.md out of my dataset's directory I ran into the same problem again.

### Code to reproduce issue

Provide a reproducible test case that is the bare minimum necessary to generate
the problem.

### Other info / logs

Include any logs or source code that would be helpful to diagnose the problem.
If including tracebacks, please include the full traceback. Large logs and
files should be attached. Please do not use screenshots for sharing text. Code
snippets should be used instead when providing tracebacks, logs, etc.

### 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.
-   [ ] No. I cannot contribute a bug fix at this time.



Discussion:
Not really a bug because the code supports what is explicitly [documented here](https://voxel51.com/docs/fiftyone/user_guide/dataset_creation/datasets.html#vocdetectiondataset), but I agree it would be nice to make the importer "smarter" so that non-XML files are silently ignored.
@brimoor How should the smarter import work? Should it display a warning/throw an error? Or should it be able to handle this specific case with annotations and images mixed in the same folder?
I think it would satisfy OP's request if this line simply omitted non-XML files. Want to make a PR??? 🤗 

https://github.com/voxel51/fiftyone/blob/8d3aac58375352e856bf1034148012fb4da2e455/fiftyone/utils/voc.py#L191
Perhaps it would be polite to, in addition, issue a warning that one or more non-XML files is being ignored.

Also, this same update would apply to all other [import formats](https://voxel51.com/docs/fiftyone/user_guide/dataset_creation/datasets.html) that have per-media labels files, which, after a quick glance, are these:

**KITTIDetectionDataset**
https://github.com/voxel51/fiftyone/blob/8d3aac58375352e856bf1034148012fb4da2e455/fiftyone/utils/voc.py#L191

**CVATVideoDataset**
https://github.com/voxel51/fiftyone/blob/8d3aac58375352e856bf1034148012fb4da2e455/fiftyone/utils/cvat.py#L768

Ok, thanks for the feedback! I'll make a PR this week :ok_hand: 

Resolving patch:
diff --git a/fiftyone/utils/cvat.py b/fiftyone/utils/cvat.py
--- a/fiftyone/utils/cvat.py
+++ b/fiftyone/utils/cvat.py
@@ -767,6 +767,7 @@ def setup(self):
             labels_paths_map = {
                 os.path.splitext(p)[0]: os.path.join(labels_path, p)
                 for p in etau.list_files(labels_path, recursive=True)
+                if etau.has_extension(p, ".xml")
             }
         else:
             labels_paths_map = {}
diff --git a/fiftyone/utils/kitti.py b/fiftyone/utils/kitti.py
--- a/fiftyone/utils/kitti.py
+++ b/fiftyone/utils/kitti.py
@@ -173,6 +173,7 @@ def setup(self):
             labels_paths_map = {
                 os.path.splitext(p)[0]: os.path.join(labels_path, p)
                 for p in etau.list_files(labels_path, recursive=True)
+                if etau.has_extension(p, ".txt")
             }
         else:
             labels_paths_map = {}
diff --git a/fiftyone/utils/voc.py b/fiftyone/utils/voc.py
--- a/fiftyone/utils/voc.py
+++ b/fiftyone/utils/voc.py
@@ -190,6 +190,7 @@ def setup(self):
             labels_paths_map = {
                 os.path.splitext(p)[0]: os.path.join(labels_path, p)
                 for p in etau.list_files(labels_path, recursive=True)
+                if etau.has_extension(p, ".xml")
             }
         else:
             labels_paths_map = {}


Test patch: