`os.path.join` drive-letter heuristic silently breaks `_do_python_eval`'s annotation path on Windows
VOCDetection._do_python_eval in yolox/data/datasets/voc.py builds a per-image annotation path template like this:
annopath = os.path.join(rootpath, "Annotations", "{:s}.xml")On Windows, this silently discards rootpath and "Annotations", leaving annopath as just "{:s}.xml".
Root cause
Windows' os.path.join (ntpath.join) treats any path component whose second character is a colon as a drive-letter path (e.g. C:), and resets the join to start fresh from that component, discarding everything joined before it. The literal template string "{:s}.xml" has a colon at index 1 purely by coincidence of Python's format-spec syntax - ntpath can't tell that apart from an actual drive reference, so it collapses the whole path.
Confirmed directly:
>>> import os
>>> os.path.join(r"C:\a\b", "Annotations", "{:s}.xml")
'{:s}.xml'This later crashes in voc_eval.py's parse_rec(annopath.format(imagename)) with FileNotFoundError, since the resulting path is just a bare filename with no directory. It only surfaces once evaluation actually reaches the per-class VOC mAP calculation (training and the results-file-writing step both succeed first), which may be why it's gone unnoticed - it only shows up on Windows, and only past that point.
Environment
- OS: Windows 11
- YOLOX: fresh clone of
main, installed viapip install -e . - Triggered during
tools/train.py's periodic evaluation (eval_interval), specifically inside_do_python_eval->voc_eval->parse_rec
Steps to reproduce
- On Windows, train on any VOC-format dataset far enough to trigger an evaluation pass.
- Traceback:
File ".../yolox/evaluators/voc_eval.py", line 92, in voc_eval
recs[imagename] = parse_rec(annopath.format(imagename))
File ".../xml/etree/ElementTree.py", line 558, in parse
source = open(source, "rb")
FileNotFoundError: [Errno 2] No such file or directory: '<imagename>.xml'Suggested fix
Don't pass the format-string literal into os.path.join at all - build the directory first, then append the template via plain string concatenation:
annopath = os.path.join(rootpath, "Annotations") + os.sep + "{:s}.xml"Verified this resolves the crash and parse_rec opens the correct file (tested against a real annotation file, with the resulting path formatting and parsing correctly).
Source: Megvii-BaseDetection/YOLOX