|
| 1 | +import argparse |
| 2 | +import os.path |
| 3 | +from os.path import join |
| 4 | +from ctc_metrics.metrics import valid |
| 5 | +from ctc_metrics.metrics import ALL_METRICS |
| 6 | +from ctc_metrics.utils.handle_results import print_results, store_results |
| 7 | +from ctc_metrics.utils.filesystem import parse_directories, read_tracking_file, load_ctmc_bounding_boxes |
| 8 | +from ctc_metrics.utils.representations import match_bboxes |
| 9 | +from ctc_metrics.scripts.evaluate import calculate_metrics |
| 10 | + |
| 11 | +def match_computed_to_reference_masks( |
| 12 | + ref_boxes: list, |
| 13 | + comp_boxes: list, |
| 14 | +): |
| 15 | + """ |
| 16 | + Matches computed masks to reference masks. |
| 17 | +
|
| 18 | + Args: |
| 19 | + ref_boxes: The reference masks. A list of lists of the reference bboxes. [frame, id, x, y, w, h] |
| 20 | + comp_boxes: The computed masks. A list of lists of the computed bboxes. [frame, id, x, y, w, h] |
| 21 | +
|
| 22 | +
|
| 23 | + Returns: |
| 24 | + The results stored in a dictionary. The dictionary contains the |
| 25 | + following keys: |
| 26 | + - labels_ref: The reference labels. A list of lists containing |
| 27 | + the labels of the reference masks. |
| 28 | + - labels_comp: The computed labels. A list of lists containing |
| 29 | + the labels of the computed masks. |
| 30 | + - mapped_ref: The mapped reference labels. A list of lists |
| 31 | + containing the mapped labels of the reference masks. |
| 32 | + - mapped_comp: The mapped computed labels. A list of lists |
| 33 | + containing the mapped labels of the computed masks. |
| 34 | + - ious: The intersection over union values. A list of lists |
| 35 | + containing the intersection over union values between mapped |
| 36 | + reference and computed masks. |
| 37 | + """ |
| 38 | + labels_ref, labels_comp, mapped_ref, mapped_comp, ious = [], [], [], [], [] |
| 39 | + |
| 40 | + matches = [match_bboxes(*x) for x in zip(ref_boxes, comp_boxes)] |
| 41 | + for match in matches: |
| 42 | + labels_ref.append(match[0]) |
| 43 | + labels_comp.append(match[1]) |
| 44 | + mapped_ref.append(match[2]) |
| 45 | + mapped_comp.append(match[3]) |
| 46 | + ious.append(match[4]) |
| 47 | + return { |
| 48 | + "labels_ref": labels_ref, |
| 49 | + "labels_comp": labels_comp, |
| 50 | + "mapped_ref": mapped_ref, |
| 51 | + "mapped_comp": mapped_comp, |
| 52 | + "ious": ious |
| 53 | + } |
| 54 | + |
| 55 | + |
| 56 | +def load_data( |
| 57 | + res: str, |
| 58 | + gt: str, |
| 59 | +): |
| 60 | + """ |
| 61 | + Load data that is necessary to calculate metrics from the given directories. |
| 62 | +
|
| 63 | + Args: |
| 64 | + res: The path to the results. |
| 65 | + gt: The path to the ground truth. |
| 66 | + trajectory_data: A flag if trajectory data is available. |
| 67 | + segmentation_data: A flag if segmentation data is available. |
| 68 | + threads: The number of threads to use. If 0, the number of threads |
| 69 | + is set to the number of available CPUs. |
| 70 | +
|
| 71 | + Returns: |
| 72 | + The computed tracks, the reference tracks, the trajectory data, the |
| 73 | + segmentation data, the computed masks and a flag if the results are |
| 74 | + valid. |
| 75 | +
|
| 76 | + """ |
| 77 | + # Read tracking files and parse mask files |
| 78 | + comp_tracking_file = join(res, "TRA", "res_track.txt") |
| 79 | + assert os.path.exists(comp_tracking_file), f"{comp_tracking_file} does not exist." |
| 80 | + comp_tracks = read_tracking_file(comp_tracking_file) |
| 81 | + ref_tracking_file = join(gt, "TRA", "man_track.txt") |
| 82 | + assert os.path.exists(ref_tracking_file), f"{ref_tracking_file} does not exist." |
| 83 | + ref_tracks = read_tracking_file(ref_tracking_file) |
| 84 | + comp_bb_file = join(res, "res", "res.txt") |
| 85 | + assert os.path.exists(comp_bb_file), f"{comp_bb_file} does not exist." |
| 86 | + comp_masks = load_ctmc_bounding_boxes(comp_bb_file) |
| 87 | + ref_bb_file = join(gt, "gt", "gt.txt") |
| 88 | + assert os.path.exists(ref_bb_file), f"{ref_bb_file} does not exist." |
| 89 | + ref_tra_masks = load_ctmc_bounding_boxes(ref_bb_file) |
| 90 | + assert len(ref_tra_masks) > 0, f"{gt}: Ground truth masks is 0!)" |
| 91 | + assert len(ref_tra_masks) == len(comp_masks), ( |
| 92 | + f"{res}: Number of result masks ({len(comp_masks)}) unequal to " |
| 93 | + f"the number of ground truth masks ({len(ref_tra_masks)})!)") |
| 94 | + # Match golden truth tracking masks to result masks |
| 95 | + traj = match_computed_to_reference_masks(ref_tra_masks, comp_masks) |
| 96 | + is_valid = valid(None, comp_tracks, traj["labels_comp"]) |
| 97 | + # Match golden truth segmentation masks to result masks |
| 98 | + return comp_tracks, ref_tracks, traj, comp_masks, is_valid |
| 99 | + |
| 100 | + |
| 101 | +def evaluate_sequence( |
| 102 | + res: str, |
| 103 | + gt: str, |
| 104 | + metrics: list = None, |
| 105 | + ): |
| 106 | + """ |
| 107 | + Evaluates a single sequence. |
| 108 | +
|
| 109 | + Args: |
| 110 | + res: The path to the results. |
| 111 | + gt: The path to the ground truth. |
| 112 | + metrics: The metrics to evaluate. |
| 113 | + threads: The number of threads to use. If 0, the number of threads |
| 114 | + is set to the number of available CPUs. |
| 115 | +
|
| 116 | + Returns: |
| 117 | + The results stored in a dictionary. |
| 118 | + """ |
| 119 | + |
| 120 | + print("Evaluate sequence: ", res, " with ground truth: ", gt, end="") |
| 121 | + # Verify all metrics |
| 122 | + if metrics is None: |
| 123 | + metrics = ALL_METRICS |
| 124 | + if "SEG" in metrics: |
| 125 | + metrics.remove("SEG") # SEG is not existing for CTMC |
| 126 | + |
| 127 | + |
| 128 | + comp_tracks, ref_tracks, traj, _, is_valid = load_data(res, gt) |
| 129 | + |
| 130 | + results = calculate_metrics( |
| 131 | + comp_tracks, ref_tracks, traj, {}, metrics, is_valid) |
| 132 | + |
| 133 | + print("with results: ", results, " done!") |
| 134 | + |
| 135 | + return results |
| 136 | + |
| 137 | + |
| 138 | +def evaluate_all( |
| 139 | + res_root: str, |
| 140 | + gt_root: str, |
| 141 | + metrics: list = None, |
| 142 | + ): |
| 143 | + """ |
| 144 | + Evaluate all sequences in a directory |
| 145 | +
|
| 146 | + Args: |
| 147 | + res_root: The root directory of the results. |
| 148 | + gt_root: The root directory of the ground truth. |
| 149 | + metrics: The metrics to evaluate. |
| 150 | + threads: The number of threads to use. If 0, the number of threads |
| 151 | + is set to the number of available CPUs. |
| 152 | +
|
| 153 | + Returns: |
| 154 | + The results stored in a dictionary. |
| 155 | + """ |
| 156 | + results = [] |
| 157 | + ret = parse_directories(res_root, gt_root) |
| 158 | + for res, gt, name in zip(*ret): |
| 159 | + results.append([name, evaluate_sequence(res, gt, metrics)]) |
| 160 | + return results |
| 161 | + |
| 162 | + |
| 163 | +def parse_args(): |
| 164 | + """ Parse arguments """ |
| 165 | + parser = argparse.ArgumentParser(description='Evaluates CTC-Sequences.') |
| 166 | + parser.add_argument('--res', type=str, required=True) |
| 167 | + parser.add_argument('--gt', type=str, required=True) |
| 168 | + parser.add_argument('-r', '--recursive', action="store_true") |
| 169 | + parser.add_argument('--csv-file', type=str, default=None) |
| 170 | + parser.add_argument('-n', '--num-threads', type=int, default=0) |
| 171 | + parser.add_argument('--valid', action="store_true") |
| 172 | + parser.add_argument('--det', action="store_true") |
| 173 | + parser.add_argument('--seg', action="store_true") |
| 174 | + parser.add_argument('--tra', action="store_true") |
| 175 | + parser.add_argument('--ct', action="store_true") |
| 176 | + parser.add_argument('--tf', action="store_true") |
| 177 | + parser.add_argument('--bc', action="store_true") |
| 178 | + parser.add_argument('--cca', action="store_true") |
| 179 | + parser.add_argument('--mota', action="store_true") |
| 180 | + parser.add_argument('--hota', action="store_true") |
| 181 | + parser.add_argument('--idf1', action="store_true") |
| 182 | + parser.add_argument('--chota', action="store_true") |
| 183 | + parser.add_argument('--mtml', action="store_true") |
| 184 | + parser.add_argument('--faf', action="store_true") |
| 185 | + parser.add_argument('--lnk', action="store_true") |
| 186 | + args = parser.parse_args() |
| 187 | + return args |
| 188 | + |
| 189 | + |
| 190 | +def main(): |
| 191 | + """ |
| 192 | + Main function that is called when the script is executed. |
| 193 | + """ |
| 194 | + args = parse_args() |
| 195 | + # Prepare metric selection |
| 196 | + metrics = [metric for metric, flag in ( |
| 197 | + ("Valid", args.valid), |
| 198 | + ("DET", args.det), |
| 199 | + ("SEG", False), |
| 200 | + ("TRA", args.tra), |
| 201 | + ("CT", args.ct), |
| 202 | + ("TF", args.tf), |
| 203 | + ("BC", args.bc), |
| 204 | + ("CCA", args.cca), |
| 205 | + ("MOTA", args.mota), |
| 206 | + ("HOTA", args.hota), |
| 207 | + ("CHOTA", args.chota), |
| 208 | + ("IDF1", args.idf1), |
| 209 | + ("MTML", args.mtml), |
| 210 | + ("FAF", args.faf), |
| 211 | + ("LNK", args.lnk), |
| 212 | + ) if flag] |
| 213 | + metrics = metrics if metrics else None |
| 214 | + # Evaluate sequence or whole directory |
| 215 | + if args.recursive: |
| 216 | + res = evaluate_all( |
| 217 | + res_root=args.res, gt_root=args.gt, metrics=metrics, |
| 218 | + ) |
| 219 | + else: |
| 220 | + res = evaluate_sequence( |
| 221 | + res=args.res, gt=args.gt, metrics=metrics) |
| 222 | + # Visualize and store results |
| 223 | + print_results(res) |
| 224 | + if args.csv_file is not None: |
| 225 | + store_results(args.csv_file, res) |
| 226 | + |
| 227 | + |
| 228 | +if __name__ == "__main__": |
| 229 | + main() |
0 commit comments