CoolFace
Apppublic

VerokeAI/Object_tracking_boxmot

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
val.py601 linesDownload Raw Back to tracking
1# Mikel Broström 🔥 Yolo Tracking 🧾 AGPL-3.0 license2 3import argparse4import subprocess5from pathlib import Path6import numpy as np7from tqdm import tqdm8import configparser9import shutil10import json11import queue12import select13import re14import os15import torch16from functools import partial17import threading18import sys19import copy20import concurrent.futures21 22from boxmot import TRACKERS23from boxmot.tracker_zoo import create_tracker24from boxmot.utils import ROOT, WEIGHTS, TRACKER_CONFIGS, logger as LOGGER, EXAMPLES, DATA25from boxmot.utils.checks import RequirementsChecker26from boxmot.utils.torch_utils import select_device27from boxmot.utils.misc import increment_path28from boxmot.postprocessing.gsi import gsi29 30from ultralytics import YOLO31from ultralytics.data.loaders import LoadImagesAndVideos32 33from tracking.detectors import (get_yolo_inferer, default_imgsz,34                                is_ultralytics_model, is_yolox_model)35from tracking.utils import convert_to_mot_format, write_mot_results, download_mot_eval_tools, download_mot_dataset, unzip_mot_dataset, eval_setup, split_dataset36from boxmot.appearance.reid.auto_backend import ReidAutoBackend37 38checker = RequirementsChecker()39checker.check_packages(('ultralytics @ git+https://github.com/mikel-brostrom/ultralytics.git', ))  # install40 41 42def cleanup_mot17(data_dir, keep_detection='FRCNN'):43    """44    Cleans up the MOT17 dataset to resemble the MOT16 format by keeping only one detection folder per sequence.45    Skips sequences that have already been cleaned.46 47    Args:48    - data_dir (str): Path to the MOT17 train directory.49    - keep_detection (str): Detection type to keep (options: 'DPM', 'FRCNN', 'SDP'). Default is 'DPM'.50    """51 52    # Get all folders in the train directory53    all_dirs = [d for d in os.listdir(data_dir) if os.path.isdir(os.path.join(data_dir, d))]54 55    # Identify unique sequences by removing detection suffixes56    unique_sequences = set(seq.split('-')[0] + '-' + seq.split('-')[1] for seq in all_dirs)57 58    for seq in unique_sequences:59        # Directory path to the cleaned sequence60        cleaned_seq_dir = os.path.join(data_dir, seq)61 62        # Skip if the sequence is already cleaned63        if os.path.exists(cleaned_seq_dir):64            print(f"Sequence {seq} is already cleaned. Skipping.")65            continue66 67        # Directories for each detection method68        seq_dirs = [os.path.join(data_dir, d)69                    for d in all_dirs if d.startswith(seq)]70 71        # Directory path for the detection folder to keep72        keep_dir = os.path.join(data_dir, f"{seq}-{keep_detection}")73 74        if os.path.exists(keep_dir):75            # Move the directory to a new name (removing the detection suffix)76            shutil.move(keep_dir, cleaned_seq_dir)77            print(f"Moved {keep_dir} to {cleaned_seq_dir}")78 79            # Remove other detection directories80            for seq_dir in seq_dirs:81                if os.path.exists(seq_dir) and seq_dir != keep_dir:82                    shutil.rmtree(seq_dir)83                    print(f"Removed {seq_dir}")84        else:85            print(f"Directory for {seq} with {keep_detection} detection does not exist. Skipping.")86 87    print("MOT17 Cleanup completed!")88 89 90def prompt_overwrite(path_type: str, path: str, ci: bool = True) -> bool:91    """92    Prompts the user to confirm overwriting an existing file.93 94    Args:95        path_type (str): Type of the path (e.g., 'Detections and Embeddings', 'MOT Result').96        path (str): The path to check.97        ci (bool): If True, automatically reuse existing file without prompting (for CI environments).98 99    Returns:100        bool: True if user confirms to overwrite, False otherwise.101    """102    if ci:103        LOGGER.debug(f"{path_type} {path} already exists. Use existing due to no UI mode.")104        return False105 106    def input_with_timeout(prompt, timeout=3.0):107        print(prompt, end='', flush=True)108 109        result = []110        input_received = threading.Event()111 112        def get_input():113            user_input = sys.stdin.readline().strip().lower()114            result.append(user_input)115            input_received.set()116 117        input_thread = threading.Thread(target=get_input)118        input_thread.daemon = True  # Ensure thread does not prevent program exit119        input_thread.start()120        input_thread.join(timeout)121 122        if input_received.is_set():123            return result[0] in ['y', 'yes']124        else:125            print("\nNo response, not proceeding with overwrite...")126            return False127 128    return input_with_timeout(f"{path_type} {path} already exists. Overwrite? [y/N]: ")129 130 131def generate_dets_embs(args: argparse.Namespace, y: Path, source: Path) -> None:132    """133    Generates detections and embeddings for the specified 134    arguments, YOLO model and source.135 136    Args:137        args (Namespace): Parsed command line arguments.138        y (Path): Path to the YOLO model file.139        source (Path): Path to the source directory.140    """141    WEIGHTS.mkdir(parents=True, exist_ok=True)142 143    if args.imgsz is None:144        args.imgsz = default_imgsz(y)145 146    yolo = YOLO(147        y if is_ultralytics_model(y)148        else 'yolov8n.pt',149    )150 151    results = yolo(152        source=source,153        conf=args.conf,154        iou=args.iou,155        agnostic_nms=args.agnostic_nms,156        stream=True,157        device=args.device,158        verbose=False,159        exist_ok=args.exist_ok,160        project=args.project,161        name=args.name,162        classes=args.classes,163        imgsz=args.imgsz,164        vid_stride=args.vid_stride,165    )166 167    if not is_ultralytics_model(y):168        m = get_yolo_inferer(y)169        yolo_model = m(model=y, device=yolo.predictor.device,170                       args=yolo.predictor.args)171        yolo.predictor.model = yolo_model172 173        # If current model is YOLOX, change the preprocess and postprocess174        if is_yolox_model(y):175            # add callback to save image paths for further processing176            yolo.add_callback("on_predict_batch_start",177                              lambda p: yolo_model.update_im_paths(p))178            yolo.predictor.preprocess = (179                lambda im: yolo_model.preprocess(im=im))180            yolo.predictor.postprocess = (181                lambda preds, im, im0s:182                yolo_model.postprocess(preds=preds, im=im, im0s=im0s))183 184    reids = []185    for r in args.reid_model:186        reid_model = ReidAutoBackend(weights=args.reid_model,187                                     device=yolo.predictor.device,188                                     half=args.half).model189        reids.append(reid_model)190        embs_path = args.project / 'dets_n_embs' / y.stem / 'embs' / r.stem / (source.parent.name + '.txt')191        embs_path.parent.mkdir(parents=True, exist_ok=True)192        embs_path.touch(exist_ok=True)193 194        if os.path.getsize(embs_path) > 0:195            open(embs_path, 'w').close()196 197    yolo.predictor.custom_args = args198 199    dets_path = args.project / 'dets_n_embs' / y.stem / 'dets' / (source.parent.name + '.txt')200    dets_path.parent.mkdir(parents=True, exist_ok=True)201    dets_path.touch(exist_ok=True)202 203    if os.path.getsize(dets_path) > 0:204        open(dets_path, 'w').close()205 206    with open(str(dets_path), 'ab+') as f:207        np.savetxt(f, [], fmt='%f', header=str(source))208 209    for frame_idx, r in enumerate(tqdm(results, desc="Frames")):210        nr_dets = len(r.boxes)211        frame_idx = torch.full((1, 1), frame_idx + 1).repeat(nr_dets, 1)212        img = r.orig_img213 214        dets = np.concatenate(215            [216                frame_idx,217                r.boxes.xyxy.to('cpu'),218                r.boxes.conf.unsqueeze(1).to('cpu'),219                r.boxes.cls.unsqueeze(1).to('cpu'),220            ], axis=1221        )222 223        # Filter dets with incorrect boxes: (x2 < x1 or y2 < y1)224        boxes = r.boxes.xyxy.to('cpu').numpy().round().astype(int)225        boxes_filter = ((np.maximum(0, boxes[:, 0]) < np.minimum(boxes[:, 2], img.shape[1])) &226                        (np.maximum(0, boxes[:, 1]) < np.minimum(boxes[:, 3], img.shape[0])))227        dets = dets[boxes_filter]228 229        with open(str(dets_path), 'ab+') as f:230            np.savetxt(f, dets, fmt='%f')231 232        for reid, reid_model_name in zip(reids, args.reid_model):233            embs = reid.get_features(dets[:, 1:5], img)234            embs_path = args.project / "dets_n_embs" / y.stem / 'embs' / reid_model_name.stem / (source.parent.name + '.txt')235            with open(str(embs_path), 'ab+') as f:236                np.savetxt(f, embs, fmt='%f')237 238 239def generate_mot_results(args: argparse.Namespace, config_dict: dict = None) -> dict[str, np.ndarray]:240    """241    Generates MOT results for the specified arguments and configuration.242 243    Args:244        args (Namespace): Parsed command line arguments.245        config_dict (dict, optional): Additional configuration dictionary.246 247    Returns:248        dict[str, np.ndarray]: {seq_name: array} with frame ids used for MOT249    """250    args.device = select_device(args.device)251    tracker = create_tracker(252        args.tracking_method,253        TRACKER_CONFIGS / (args.tracking_method + '.yaml'),254        args.reid_model[0].with_suffix('.pt'),255        args.device,256        False,257        False,258        config_dict259    )260 261    with open(args.dets_file_path, 'r') as file:262        source = Path(file.readline().strip().replace("# ", ""))263 264    dets = np.loadtxt(args.dets_file_path, skiprows=1)265    embs = np.loadtxt(args.embs_file_path)266 267    dets_n_embs = np.concatenate([dets, embs], axis=1)268 269    dataset = LoadImagesAndVideos(source)270 271    txt_path = args.exp_folder_path / (source.parent.name + '.txt')272    all_mot_results = []273 274    # Change FPS275    if args.fps:276 277        # Extract original FPS278        conf_path = source.parent / 'seqinfo.ini'279        conf = configparser.ConfigParser()280        conf.read(conf_path)281 282        orig_fps = int(conf.get("Sequence", "frameRate"))283    284        if orig_fps < args.fps:285            LOGGER.warning(f"Original FPS ({orig_fps}) is lower than "286                           f"requested FPS ({args.fps}) for sequence "287                           f"{source.parent.name}. Using original FPS.")288            target_fps = orig_fps289        else:290            target_fps = args.fps291 292        293        step = orig_fps/target_fps294    else:295        step = 1296    297    # Create list with frame numbers according to needed step298    frame_nums = np.arange(1, len(dataset) + 1, step).astype(int).tolist()299 300    seq_frame_nums = {source.parent.name: frame_nums.copy()}301 302    for frame_num, d in enumerate(tqdm(dataset, desc=source.parent.name), 1):303        # Filter using list with needed numbers304        if len(frame_nums) > 0:305            if frame_num < frame_nums[0]:306                continue307            else:308                frame_nums.pop(0)309 310        im = d[1][0]311        frame_dets_n_embs = dets_n_embs[dets_n_embs[:, 0] == frame_num]312 313        dets = frame_dets_n_embs[:, 1:7]314        embs = frame_dets_n_embs[:, 7:]315        tracks = tracker.update(dets, im, embs)316 317        if tracks.size > 0:318            mot_results = convert_to_mot_format(tracks, frame_num)319            all_mot_results.append(mot_results)320 321    if all_mot_results:322        all_mot_results = np.vstack(all_mot_results)323    else:324        all_mot_results = np.empty((0, 0))325 326    write_mot_results(txt_path, all_mot_results)327 328    return seq_frame_nums329 330 331def parse_mot_results(results: str) -> dict:332    """333    Extracts the COMBINED HOTA, MOTA, IDF1 from the results generated by the run_mot_challenge.py script.334 335    Args:336        results (str): MOT results as a string.337 338    Returns:339        dict: A dictionary containing HOTA, MOTA, and IDF1 scores.340    """341    combined_results = results.split('COMBINED')[2:-1]342    combined_results = [float(re.findall(r"[-+]?(?:\d*\.*\d+)", f)[0])343                        for f in combined_results]344 345    results_dict = {}346    for key, value in zip(["HOTA", "MOTA", "IDF1"], combined_results):347        results_dict[key] = value348 349    return results_dict350 351 352def trackeval(args: argparse.Namespace, seq_paths: list, save_dir: Path, MOT_results_folder: Path, gt_folder: Path, metrics: list = ["HOTA", "CLEAR", "Identity"]) -> str:353    """354    Executes a Python script to evaluate MOT challenge tracking results using specified metrics.355 356    Args:357        seq_paths (list): List of sequence paths.358        save_dir (Path): Directory to save evaluation results.359        MOT_results_folder (Path): Folder containing MOT results.360        gt_folder (Path): Folder containing ground truth data.361        metrics (list, optional): List of metrics to use for evaluation. Defaults to ["HOTA", "CLEAR", "Identity"].362 363    Returns:364        str: Standard output from the evaluation script.365    """366 367    d = [seq_path.parent.name for seq_path in seq_paths]368 369    args = [370        sys.executable, EXAMPLES / 'val_utils' / 'scripts' / 'run_mot_challenge.py',371        "--GT_FOLDER", str(gt_folder),372        "--BENCHMARK", "",373        "--TRACKERS_FOLDER", args.exp_folder_path,374        "--TRACKERS_TO_EVAL", "",375        "--SPLIT_TO_EVAL", "train",376        "--METRICS", *metrics,377        "--USE_PARALLEL", "True",378        "--TRACKER_SUB_FOLDER", "",379        "--NUM_PARALLEL_CORES", str(4),380        "--SKIP_SPLIT_FOL", "True",381        "--GT_LOC_FORMAT", "{gt_folder}/{seq}/gt/gt_temp.txt",382        "--SEQ_INFO", *d383    ]384 385    p = subprocess.Popen(386        args=args,387        stdout=subprocess.PIPE,388        stderr=subprocess.PIPE,389        text=True390    )391 392    stdout, stderr = p.communicate()393 394    if stderr:395        print("Standard Error:\n", stderr)396    return stdout397 398 399def run_generate_dets_embs(opt: argparse.Namespace) -> None:400    """401    Runs the generate_dets_embs function for all YOLO models and source directories.402 403    Args:404        opt (Namespace): Parsed command line arguments.405    """406    mot_folder_paths = sorted([item for item in Path(opt.source).iterdir()])407    for y in opt.yolo_model:408        for i, mot_folder_path in enumerate(mot_folder_paths):409            dets_path = Path(opt.project) / 'dets_n_embs' / y.stem / 'dets' / (mot_folder_path.name + '.txt')410            embs_path = Path(opt.project) / 'dets_n_embs' / y.stem / 'embs' / (opt.reid_model[0].stem) / (mot_folder_path.name + '.txt')411            if dets_path.exists() and embs_path.exists():412                if prompt_overwrite('Detections and Embeddings', dets_path, opt.ci):413                    LOGGER.debug(f'Overwriting detections and embeddings for {mot_folder_path}...')414                else:415                    LOGGER.debug(f'Skipping generation for {mot_folder_path} as they already exist.')416                    continue417            LOGGER.debug(f'Generating detections and embeddings for data under {mot_folder_path} [{i + 1}/{len(mot_folder_paths)} seqs]')418            generate_dets_embs(opt, y, source=mot_folder_path / 'img1')419 420 421def process_single_mot(opt: argparse.Namespace, d: Path, e: Path, evolve_config: dict):422    # Create a deep copy of opt so each task works independently423    new_opt = copy.deepcopy(opt)424    new_opt.dets_file_path = d425    new_opt.embs_file_path = e426    frames_dict = generate_mot_results(new_opt, evolve_config)427    return frames_dict428 429def run_generate_mot_results(opt: argparse.Namespace, evolve_config: dict = None) -> None:430    """431    Runs the generate_mot_results function for all YOLO models and detection/embedding files432    in parallel.433    """434    435    for y in opt.yolo_model:436        exp_folder_path = opt.project / 'mot' / (f"{y.stem}_{opt.reid_model[0].stem}_{opt.tracking_method}")437        exp_folder_path = increment_path(path=exp_folder_path, sep="_", exist_ok=False)438        opt.exp_folder_path = exp_folder_path439 440        mot_folder_names = [item.stem for item in Path(opt.source).iterdir()]441        442        dets_folder = opt.project / "dets_n_embs" / y.stem / 'dets'443        embs_folder = opt.project / "dets_n_embs" / y.stem / 'embs' / opt.reid_model[0].stem444        445        dets_file_paths = sorted([446            item for item in dets_folder.glob('*.txt')447            if not item.name.startswith('.') and item.stem in mot_folder_names448        ])449        embs_file_paths = sorted([450            item for item in embs_folder.glob('*.txt')451            if not item.name.startswith('.') and item.stem in mot_folder_names452        ])453        454        LOGGER.info(f"\nStarting tracking on:\n\t{opt.source}\nwith preloaded dets\n\t({dets_folder.relative_to(ROOT)})\nand embs\n\t({embs_folder.relative_to(ROOT)})\nusing\n\t{opt.tracking_method}")455 456        tasks = []457        # Create a thread pool to run each file pair in parallel458        with concurrent.futures.ThreadPoolExecutor() as executor:459            for d, e in zip(dets_file_paths, embs_file_paths):460                mot_result_path = exp_folder_path / (d.stem + '.txt')461                if mot_result_path.exists():462                    if prompt_overwrite('MOT Result', mot_result_path, opt.ci):463                        LOGGER.info(f'Overwriting MOT result for {d.stem}...')464                    else:465                        LOGGER.info(f'Skipping MOT result generation for {d.stem} as it already exists.')466                        continue467                # Submit the task to process this file pair in parallel468                tasks.append(executor.submit(process_single_mot, opt, d, e, evolve_config))469            470            # Dict with {seq_name: [frame_nums]}471            seqs_frame_nums = {}472            # Wait for all tasks to complete and log any exceptions473            for future in concurrent.futures.as_completed(tasks):474                try:475                    seqs_frame_nums.update(future.result())476                except Exception as exc:477                    LOGGER.error(f'Error processing file pair: {exc}')478    479    # Postprocess data with gsi if requested480    if opt.gsi:481        gsi(mot_results_folder=opt.exp_folder_path)482 483    with open(opt.exp_folder_path / 'seqs_frame_nums.json', 'w') as f:484        json.dump(seqs_frame_nums, f)485 486 487def run_trackeval(opt: argparse.Namespace) -> dict:488    """489    Runs the trackeval function to evaluate tracking results.490 491    Args:492        opt (Namespace): Parsed command line arguments.493    """494    seq_paths, save_dir, MOT_results_folder, gt_folder = eval_setup(opt, opt.val_tools_path)495    trackeval_results = trackeval(opt, seq_paths, save_dir, MOT_results_folder, gt_folder)496    hota_mota_idf1 = parse_mot_results(trackeval_results)497    if opt.verbose:498        LOGGER.info(trackeval_results)499        with open(opt.tracking_method + "_output.json", "w") as outfile:500            outfile.write(json.dumps(hota_mota_idf1))501    LOGGER.info(json.dumps(hota_mota_idf1))502    return hota_mota_idf1503 504 505def run_all(opt: argparse.Namespace) -> None:506    """507    Runs all stages of the pipeline: generate_dets_embs, generate_mot_results, and trackeval.508 509    Args:510        opt (Namespace): Parsed command line arguments.511    """512    run_generate_dets_embs(opt)513    run_generate_mot_results(opt)514    run_trackeval(opt)515 516 517def parse_opt() -> argparse.Namespace:518    parser = argparse.ArgumentParser()519 520    # Global arguments521    parser.add_argument('--yolo-model', nargs='+', type=Path, default=[WEIGHTS / 'yolov8n.pt'], help='yolo model path')522    parser.add_argument('--reid-model', nargs='+', type=Path, default=[WEIGHTS / 'osnet_x0_25_msmt17.pt'], help='reid model path')523    parser.add_argument('--source', type=str, help='file/dir/URL/glob, 0 for webcam')524    parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=None, help='inference size h,w')525    parser.add_argument('--fps', type=int, default=None, help='video frame-rate')526    parser.add_argument('--conf', type=float, default=0.01, help='min confidence threshold')527    parser.add_argument('--iou', type=float, default=0.7, help='intersection over union (IoU) threshold for NMS')528    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')529    parser.add_argument('--classes', nargs='+', type=int, default=0, help='filter by class: --classes 0, or --classes 0 2 3')530    parser.add_argument('--project', default=ROOT / 'runs', type=Path, help='save results to project/name')531    parser.add_argument('--name', default='', help='save results to project/name')532    parser.add_argument('--exist-ok', action='store_true', default=True, help='existing project/name ok, do not increment')533    parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')534    parser.add_argument('--vid-stride', type=int, default=1, help='video frame-rate stride')535    parser.add_argument('--ci', action='store_true', help='Automatically reuse existing due to no UI in CI')536    parser.add_argument('--tracking-method', type=str, default='deepocsort', help='deepocsort, botsort, strongsort, ocsort, bytetrack, imprassoc, boosttrack')537    parser.add_argument('--dets-file-path', type=Path, help='path to detections file')538    parser.add_argument('--embs-file-path', type=Path, help='path to embeddings file')539    parser.add_argument('--exp-folder-path', type=Path, help='path to experiment folder')540    parser.add_argument('--verbose', action='store_true', help='print results')541    parser.add_argument('--agnostic-nms', default=False, action='store_true', help='class-agnostic NMS')542    parser.add_argument('--gsi', action='store_true', help='apply Gaussian smooth interpolation postprocessing')543    parser.add_argument('--n-trials', type=int, default=4, help='nr of trials for evolution')544    parser.add_argument('--objectives', type=str, nargs='+', default=["HOTA", "MOTA", "IDF1"], help='set of objective metrics: HOTA,MOTA,IDF1')545    parser.add_argument('--val-tools-path', type=Path, default=EXAMPLES / 'val_utils', help='path to store trackeval repo in')546    parser.add_argument('--split-dataset', action='store_true', help='Use the second half of the dataset')547 548    subparsers = parser.add_subparsers(dest='command')549 550    # Subparser for generate_dets_embs551    generate_dets_embs_parser = subparsers.add_parser('generate_dets_embs', help='Generate detections and embeddings')552    generate_dets_embs_parser.add_argument('--source', type=str, required=True, help='file/dir/URL/glob, 0 for webcam')553    generate_dets_embs_parser.add_argument('--yolo-model', nargs='+', type=Path, default=WEIGHTS / 'yolov8n.pt', help='yolo model path')554    generate_dets_embs_parser.add_argument('--reid-model', nargs='+', type=Path, default=WEIGHTS / 'osnet_x0_25_msmt17.pt', help='reid model path')555    generate_dets_embs_parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')556    generate_dets_embs_parser.add_argument('--classes', nargs='+', type=int, default=0, help='filter by class: --classes 0, or --classes 0 2 3')557 558    # Subparser for generate_mot_results559    generate_mot_results_parser = subparsers.add_parser('generate_mot_results', help='Generate MOT results')560    generate_mot_results_parser.add_argument('--yolo-model', nargs='+', type=Path, default=WEIGHTS / 'yolov8n.pt', help='yolo model path')561    generate_mot_results_parser.add_argument('--reid-model', nargs='+', type=Path, default=WEIGHTS / 'osnet_x0_25_msmt17.pt', help='reid model path')562    generate_mot_results_parser.add_argument('--tracking-method', type=str, default='deepocsort', help='deepocsort, botsort, strongsort, ocsort, bytetrack, imprassoc, boosttrack')563    generate_mot_results_parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')564 565    # Subparser for trackeval566    trackeval_parser = subparsers.add_parser('trackeval', help='Evaluate tracking results')567    trackeval_parser.add_argument('--source', type=str, required=True, help='file/dir/URL/glob, 0 for webcam')568    trackeval_parser.add_argument('--exp-folder-path', type=Path, required=True, help='path to experiment folder')569 570    opt = parser.parse_args()571    source_path = Path(opt.source)572    opt.benchmark, opt.split = source_path.parent.name, source_path.name573 574    return opt575 576 577if __name__ == "__main__":578    opt = parse_opt()579    580    # download MOT benchmark581    download_mot_eval_tools(opt.val_tools_path)582 583    if not Path(opt.source).exists():584        zip_path = download_mot_dataset(opt.val_tools_path, opt.benchmark)585        unzip_mot_dataset(zip_path, opt.val_tools_path, opt.benchmark)586 587    if opt.benchmark == 'MOT17':588        cleanup_mot17(opt.source)589 590    if opt.split_dataset:591        opt.source, opt.benchmark = split_dataset(opt.source)592 593    if opt.command == 'generate_dets_embs':594        run_generate_dets_embs(opt)595    elif opt.command == 'generate_mot_results':596        run_generate_mot_results(opt)597    elif opt.command == 'trackeval':598        run_trackeval(opt)599    else:600        run_all(opt)601