"""
Copyright (c) 2021-2022 UCLouvain, ICTEAM
Licensed under GPL-3.0 [see LICENSE for details]
Written by Jonathan Samelson (2021-2022), Arthur Pisvin (2023)
"""
import ast
import logging
log = logging.getLogger("aptitude-toolbox")
valid_preproc_keys = ["border", "resize", "roi"]
valid_postproc_keys = ["coi", "nms", "min_conf", "max_height", "min_height",
"max_width", "min_width", "min_area", "top_k", "resize_results", "roi"]
valid_nms_values = ["cv2", "Malisiewicz"]
valid_proc_keys = ["task", "output_type", "model_type", "pref_implem", "params"]
valid_detector_type = ["YOLO4", "YOLO5", "YOLO8", "MaskRCNN", "FasterRCNN", "BackgroundSubtractor", "Detectron2"]
valid_tracker_type = ["SORT", "DeepSORT", "Centroid", "IOU"]
[docs]def validate_preprocess_parameters(pre_params: dict) -> bool:
"""Check validity and compatibility between provided preprocess parameters.
Args:
pre_params (dict): the dictionary containing preprocess parameters.
Returns:
bool: whether it is a valid configuration.
"""
if not pre_params:
return True # Empty dict for Preproc is valid
list_keys = pre_params.keys()
valid = True
for key in list_keys:
if key not in valid_preproc_keys:
log.error("{} is not a valid entry for Preproc configuration.".format(key))
valid = False
if "border" in list_keys:
if "centered" not in pre_params["border"]:
log.error("\"border\" entry without \"centered\" sub-entry.")
valid = False
if not isinstance(pre_params["border"].get("centered"), bool):
log.error("\"centered\" entry should be of type bool.")
if "resize" in list_keys:
if "width" not in pre_params["resize"]:
log.error("\"resize\" entry without \"width\" sub-entries.")
valid = False
if "height" not in pre_params["resize"]:
log.error("\"resize\" entry without \"height\" sub-entries.")
valid = False
if not isinstance(pre_params["resize"]["width"], int) \
or not isinstance(pre_params["resize"]["height"], int):
log.error("resize width or height must be of type int.")
valid = False
if pre_params["resize"]["width"] <= 0 or pre_params["resize"]["height"] <= 0:
log.error("resize width or height must be positive.")
valid = False
if "roi" in list_keys:
valid = _validate_roi_parameters(pre_params["roi"], preproc=True)
return valid
[docs]def validate_postprocess_parameters(post_params: dict) -> bool:
"""Check validity and compatibility between provided postprocess parameters.
Args:
post_params (dict): the dictionary containing postprocess parameters.
Returns:
bool: whether it is a valid configuration.
"""
if not post_params:
return True # Empty dict for postproc is valid
list_keys = post_params.keys()
valid = True
for key in list_keys:
if key not in valid_postproc_keys:
log.error("{} is not a valid entry for postproc configuration.".format(key))
valid = False
if "coi" in list_keys:
if not isinstance(post_params["coi"], str):
log.error("\"coi\" entry must be of type str.")
valid = False
else:
coi = ast.literal_eval(post_params["coi"])
if not isinstance(coi, list):
log.error("\"coi\" entry should evaluate to type list.")
valid = False
if "nms" in list_keys:
if "pref_implem" not in post_params["nms"] or "nms_thresh" not in post_params["nms"]:
log.error("\"nms\" entry without \"pref_implem\" or \"nms_tresh\" sub-entries")
valid = False
if post_params["nms"]["pref_implem"] not in valid_nms_values:
log.error("\"pref_implem\" of nms unknown. Valid values are : {}".format(valid_nms_values))
valid = False
if post_params["nms"]["nms_thresh"] < 0 or post_params["nms"]["nms_thresh"] > 1:
log.error("\"nms_thresh\" (postproc) value must be included between 0 and 1.")
valid = False
if "min_conf" in list_keys and (post_params["min_conf"] < 0 or post_params["min_conf"] > 1):
log.error("\"min_conf\" (postproc) value must be included between 0 and 1.")
valid = False
if "max_height" in list_keys and (post_params["max_height"] < 0 or post_params["max_height"] > 1):
log.error("\"max_height\" value must be included between 0 and 1.")
valid = False
if "min_height" in list_keys and (post_params["min_height"] < 0 or post_params["min_height"] > 1):
log.error("\"min_height\" value must be included between 0 and 1.")
valid = False
if "max_width" in list_keys and (post_params["max_width"] < 0 or post_params["max_width"] > 1):
log.error("\"max_width\" value must be included between 0 and 1.")
valid = False
if "min_width" in list_keys and (post_params["min_width"] < 0 or post_params["min_width"] > 1):
log.error("\"min_width\" value must be included between 0 and 1.")
valid = False
if "min_area" in list_keys and post_params["min_area"] < 0:
log.error("\"top_k\" value must be greater or equal to 0.")
valid = False
if "top_k" in list_keys and post_params["top_k"] < 0:
log.error("\"top_k\" value must be greater or equal to 0.")
valid = False
if "roi" in list_keys:
valid = _validate_roi_parameters(post_params["roi"], preproc=False)
if "resize_results" in list_keys:
if "width" not in post_params["resize_results"]:
log.error("\"resize_results\" entry without \"width\" sub-entries.")
valid = False
if "height" not in post_params["resize_results"]:
log.error("\"resize_results\" entry without \"height\" sub-entries.")
valid = False
if not isinstance(post_params["resize_results"]["width"], int) \
or not isinstance(post_params["resize_results"]["height"], int):
log.error("resize_results width or height must be of type int.")
valid = False
if post_params["resize_results"]["width"] <= 0 or post_params["resize_results"]["height"] <= 0:
log.error("resize_results width or height must be positive.")
valid = False
return valid
[docs]def _validate_roi_parameters(roi_params: dict, preproc: bool) -> bool:
valid = True
if "path" not in roi_params and "coords" not in roi_params:
log.error("\"roi\" entry without \"path\" or \"coords\" sub-entry.")
valid = False
if "path" in roi_params and not isinstance(roi_params["path"], str):
log.error("\"path\" (ROI) entry must be of type str.")
valid = False
if "coords" in roi_params:
if not isinstance(roi_params["coords"], str):
log.error("\"coords\" entry must be of type str.")
valid = False
coords = ast.literal_eval(roi_params["coords"])
if (not isinstance(coords, tuple)) or (not isinstance(coords[0], tuple)):
log.error("\"coords\" entry should evaluate to type tuple of tuples.")
valid = False
if not preproc and "max_outside_roi_thresh" not in roi_params:
log.error("\"max_outside_roi_thresh\" entry must be provided if in post proc parameters")
valid = False
if "max_outside_roi_thresh" in roi_params \
and (roi_params["max_outside_roi_thresh"] < 0 or roi_params["max_outside_roi_thresh"] > 1):
log.error("\"max_outside_roi_thresh\" must be included between 0 and 1")
valid = False
return valid
[docs]def validate_detector_parameters(det_params: dict) -> bool:
"""Check validity and compatibility between provided detector parameters.
Args:
det_params (dict): the dictionary containing detector parameters.
Returns:
bool: whether it is a valid configuration
"""
list_keys = det_params.keys()
valid = True
for key in valid_proc_keys:
if key not in list_keys:
log.error("{} is missing in proc configuration.".format(key))
valid = False
if det_params["task"] != "detection":
log.error("Detector parameters are being evaluated but \"task\" entry has not the value \"detection\"")
valid = False
if det_params["output_type"] != "bboxes2D":
log.error("Invalid \"output_type\", \"bboxes2D\" is the only supported output at the moment")
valid = False
if det_params["model_type"] not in valid_detector_type:
log.error("Invalid \"model_type\" in proc configuration.")
valid = False
if "pref_implem" not in det_params:
log.error("\"pref_implem\" is missing proc configuration.")
valid = False
elif not isinstance(det_params["pref_implem"], str):
log.error("The value of \"pref_implem\" sub-entry must be of type string.")
valid = False
if det_params["model_type"] == "YOLO4":
valid = valid and _validate_yolo4_parameters(det_params)
elif det_params["model_type"] == "YOLO5":
valid = valid and _validate_yolo5_parameters(det_params)
elif det_params["model_type"] == "YOLO8":
valid = valid and _validate_yolo8_parameters(det_params)
elif det_params["model_type"] == "Detectron2":
valid = valid and _validate_detectron2_parameters(det_params)
elif det_params["model_type"] == "MaskRCNN":
valid = valid and _validate_maskrcnn_parameters(det_params)
elif det_params["model_type"] == "FasterRCNN":
valid = valid and _validate_fasterrcnn_parameters(det_params)
elif det_params["model_type"] == "BackgroundSubtractor":
valid = valid and _validate_backgroundsubtractor_parameters(det_params)
else:
log.error("Unknown detector \"model_type\": {}".format(det_params["model_type"]))
valid = False
return valid
[docs]def _validate_yolo4_parameters(det_params: dict) -> bool:
valid = True
yolo4_params = det_params["params"]
if det_params["pref_implem"] not in ["cv2-DetectionModel", "cv2-ReadNet"]:
log.error("Unknown implementation of YOLO4: {}".format(det_params["pref_implem"]))
valid = False
if "config_path" not in yolo4_params:
log.error("\"config_path\" sub-entry is required in params for YOLO4 model type.")
valid = False
elif not isinstance(yolo4_params["config_path"], str):
log.error("The value of \"config_path\" sub-entry must be of type string.")
valid = False
if "model_path" not in yolo4_params:
log.error("\"model_path\" sub-entry is required in params for YOLO4 model type.")
valid = False
elif not isinstance(yolo4_params["model_path"], str):
log.error("The value of \"model_path\" sub-entry must be of type string.")
valid = False
if "input_width" in yolo4_params \
and not (isinstance(yolo4_params["input_width"], int) and yolo4_params["input_width"] > 0):
log.error("\"input_width\" sub-entry must be of type int and must be positive.")
valid = False
if "input_height" in yolo4_params \
and not (isinstance(yolo4_params["input_height"], int) and yolo4_params["input_height"] > 0):
log.error("\"input_height\" sub-entry must be of type int and must be positive.")
valid = False
if "conf_thresh" in yolo4_params and (yolo4_params["conf_thresh"] < 0 or yolo4_params["conf_thresh"] > 1):
log.error("\"conf_thresh\" (YOLO4 params) value must be included between 0 and 1.")
valid = False
if "nms_thresh" in yolo4_params and (yolo4_params["nms_thresh"] < 0 or yolo4_params["nms_thresh"] > 1):
log.error("\"nms_thresh\" (YOLO4 params) value must be included between 0 and 1.")
valid = False
if "nms_across_classes" in yolo4_params and not isinstance(yolo4_params.get("nms_across_classes"), bool):
log.error("\"nms_across_classes\" sub-entry must be of type bool.")
valid = False
if "GPU" in yolo4_params and not isinstance(yolo4_params.get("GPU"), bool):
log.error("\"GPU\" sub-entry must be of type bool.")
valid = False
if "half_precision" in yolo4_params and not isinstance(yolo4_params.get("half_precision"), bool):
log.error("\"half_precision\" sub-entry must be of type bool.")
valid = False
return valid
[docs]def _validate_yolo5_parameters(det_params: dict) -> bool:
valid = True
yolo5_params = det_params["params"]
if det_params["pref_implem"] != "torch-Ultralytics":
log.error("Unknown implementation of YOLO5: {}".format(det_params["pref_implem"]))
valid = False
if "model_path" not in yolo5_params:
log.error("\"model_path\" sub-entry is required in params for YOLO5 model type.")
valid = False
elif not isinstance(yolo5_params["model_path"], str):
log.error("The value of \"model_path\" sub-entry must be of type string.")
valid = False
if "input_width" in yolo5_params \
and not (isinstance(yolo5_params["input_width"], int) and yolo5_params["input_width"] > 0):
log.error("\"input_width\" sub-entry must be of type int and must be positive.")
valid = False
if "input_height" in yolo5_params \
and not (isinstance(yolo5_params["input_height"], int) and yolo5_params["input_height"] > 0):
log.error("\"input_height\" sub-entry must be of type int and must be positive.")
valid = False
if "conf_thresh" in yolo5_params and (yolo5_params["conf_thresh"] < 0 or yolo5_params["conf_thresh"] > 1):
log.error("\"conf_thresh\" (YOLO5 params) value must be included between 0 and 1.")
valid = False
if "nms_thresh" in yolo5_params and (yolo5_params["nms_thresh"] < 0 or yolo5_params["nms_thresh"] > 1):
log.error("\"nms_thresh\" (YOLO5 params) value must be included between 0 and 1.")
valid = False
if "nms_across_classes" in yolo5_params and not isinstance(yolo5_params.get("nms_across_classes"), bool):
log.error("\"nms_across_classes\" sub-entry must be of type bool.")
valid = False
if "GPU" in yolo5_params and not isinstance(yolo5_params.get("GPU"), bool):
log.error("\"GPU\" sub-entry must be of type bool.")
valid = False
return valid
[docs]def _validate_yolo8_parameters(det_params: dict) -> bool:
valid = True
yolo8_params = det_params["params"]
if det_params["pref_implem"] != "torch-Ultralytics":
log.error("Unknown implementation of YOLO5: {}".format(det_params["pref_implem"]))
valid = False
if "model_path" not in yolo8_params:
log.error("\"model_path\" sub-entry is required in params for YOLO8 model type.")
valid = False
elif not isinstance(yolo8_params["model_path"], str):
log.error("The value of \"model_path\" sub-entry must be of type string.")
valid = False
if "input_width" in yolo8_params \
and not (isinstance(yolo8_params["input_width"], int) and yolo8_params["input_width"] > 0):
log.error("\"input_width\" sub-entry must be of type int and must be positive.")
valid = False
if "input_height" in yolo8_params \
and not (isinstance(yolo8_params["input_height"], int) and yolo8_params["input_height"] > 0):
log.error("\"input_height\" sub-entry must be of type int and must be positive.")
valid = False
if "conf_thresh" in yolo8_params and (yolo8_params["conf_thresh"] < 0 or yolo8_params["conf_thresh"] > 1):
log.error("\"conf_thresh\" (YOLO5 params) value must be included between 0 and 1.")
valid = False
if "nms_thresh" in yolo8_params and (yolo8_params["nms_thresh"] < 0 or yolo8_params["nms_thresh"] > 1):
log.error("\"nms_thresh\" (YOLO5 params) value must be included between 0 and 1.")
valid = False
if "nms_across_classes" in yolo8_params and not isinstance(yolo8_params.get("nms_across_classes"), bool):
log.error("\"nms_across_classes\" sub-entry must be of type bool.")
valid = False
if "GPU" in yolo8_params and not isinstance(yolo8_params.get("GPU"), bool):
log.error("\"GPU\" sub-entry must be of type bool.")
valid = False
return valid
[docs]def _validate_backgroundsubtractor_parameters(det_params: dict) -> bool:
valid = True
if det_params["pref_implem"] not in ["mean", "median", "frame_diff"]:
log.error("Unknown implementation of BackgroundSubtractor: {}".format(det_params["pref_implem"]))
valid = False
bs_params = det_params["params"]
if "contour_thresh" in bs_params and bs_params["contour_thresh"] < 0:
log.error("\"contour_thresh\" (BackgroundSubtraction) value must be positive.")
valid = False
if "intensity" in bs_params and bs_params["intensity"] < 0:
log.error("\"intensity\" value must be positive.")
valid = False
if "max_last_images" in bs_params and bs_params["max_last_images"] < 0:
log.error("\"max_last_images\" value must be positive.")
valid = False
return valid
[docs]def _validate_detectron2_parameters(det_params: dict) -> bool:
valid = True
det2_params = det_params["params"]
if det_params["pref_implem"] != "Default":
log.error("Unknown implementation of Detectron2: {}".format(det_params["pref_implem"]))
valid = False
if "config_path" not in det2_params:
log.error("\"config_path\" sub-entry is required in params for Detectron2 model type.")
valid = False
elif not isinstance(det2_params["config_path"], str):
log.error("The value of \"config_path\" sub-entry must be of type string.")
valid = False
if "model_path" not in det2_params:
log.error("\"model_path\" sub-entry is required in params for Detectron2 model type.")
valid = False
elif not isinstance(det2_params["model_path"], str):
log.error("The value of \"model_path\" sub-entry must be of type string.")
valid = False
if "conf_thresh" in det2_params and (det2_params["conf_thresh"] < 0 or det2_params["conf_thresh"] > 1):
log.error("\"conf_thresh\" (Detectron2) value must be included between 0 and 1.")
valid = False
if "nms_thresh" in det2_params and (det2_params["nms_thresh"] < 0 or det2_params["nms_thresh"] > 1):
log.error("\"nms_thresh\" (Detectron2) value must be included between 0 and 1.")
valid = False
if "GPU" in det2_params and not isinstance(det2_params.get("GPU"), bool):
log.error("\"GPU\" sub-entry must be of type bool.")
valid = False
return valid
[docs]def _validate_maskrcnn_parameters(det_params: dict) -> bool:
valid = True
mrcnn_params = det_params["params"]
if det_params["pref_implem"] != "torch-resnet50":
log.error("Unknown implementation of MaskRCNN: {}".format(det_params["pref_implem"]))
valid = False
if "input_width" in mrcnn_params \
and not (isinstance(mrcnn_params["input_width"], int) and mrcnn_params["input_width"] > 0):
log.error("\"input_width\" sub-entry must be of type int and must be positive.")
valid = False
if "input_height" in mrcnn_params \
and not (isinstance(mrcnn_params["input_height"], int) and mrcnn_params["input_height"] > 0):
log.error("\"input_height\" sub-entry must be of type int and must be positive.")
valid = False
if "GPU" in mrcnn_params and not isinstance(mrcnn_params.get("GPU"), bool):
log.error("\"GPU\" sub-entry must be of type bool.")
valid = False
if "use_coco_weights" in mrcnn_params and not isinstance(mrcnn_params.get("use_coco_weights"), bool):
log.error("\"use_coco_weights\" sub-entry must be of type bool.")
valid = False
if "use_coco_weights" in mrcnn_params and not mrcnn_params["use_coco_weights"] \
and "model_path" not in mrcnn_params:
log.error("If \"use_coco_weights\" is set to False, \"model_path\" must be an entry of params.")
valid = False
return valid
[docs]def _validate_fasterrcnn_parameters(det_params: dict) -> bool:
valid = True
fasterrcnn_params = det_params["params"]
if det_params["pref_implem"] != "torch-resnet50":
log.error("Unknown implementation of FasterRCNN: {}".format(det_params["pref_implem"]))
valid = False
if "input_width" in fasterrcnn_params \
and not (isinstance(fasterrcnn_params["input_width"], int) and fasterrcnn_params["input_width"] > 0):
log.error("\"input_width\" sub-entry must be of type int and must be positive.")
valid = False
if "input_height" in fasterrcnn_params \
and not (isinstance(fasterrcnn_params["input_height"], int) and fasterrcnn_params["input_height"] > 0):
log.error("\"input_height\" sub-entry must be of type int and must be positive.")
valid = False
if "GPU" in fasterrcnn_params and not isinstance(fasterrcnn_params.get("GPU"), bool):
log.error("\"GPU\" sub-entry must be of type bool.")
valid = False
if "use_coco_weights" in fasterrcnn_params and not isinstance(fasterrcnn_params.get("use_coco_weights"), bool):
log.error("\"use_coco_weights\" sub-entry must be of type bool.")
valid = False
if "use_coco_weights" in fasterrcnn_params and not fasterrcnn_params["use_coco_weights"] \
and "model_path" not in fasterrcnn_params:
log.error("If \"use_coco_weights\" is set to False, \"model_path\" must be an entry of params.")
valid = False
return valid
[docs]def validate_tracker_parameters(track_params: dict) -> bool:
"""Check validity and compatibility between provided detector parameters.
Args:
track_params (dict): the dictionary containing tracker parameters.
Returns:
bool: whether it is a valid configuration
"""
list_keys = track_params.keys()
valid = True
for key in valid_proc_keys:
if key not in list_keys:
log.error("{} is missing in proc configuration.".format(key))
valid = False
if track_params["task"] != "tracking":
log.error("Detector parameters are being evaluated but \"task\" entry has not the value \"tracking\"")
valid = False
if track_params["output_type"] != "bboxes2D":
log.error("Invalid \"output_type\", \"bboxes2D\" is the only supported output at the moment")
valid = False
if track_params["model_type"] not in valid_tracker_type:
log.error("Invalid \"model_type\" in proc configuration.")
valid = False
if "pref_implem" not in track_params:
log.error("\"pref_implem\" is missing proc configuration.")
valid = False
elif not isinstance(track_params["pref_implem"], str):
log.error("The value of \"pref_implem\" sub-entry must be of type string.")
valid = False
if track_params["model_type"] == "SORT":
valid = valid and _validate_sort_parameters(track_params)
elif track_params["model_type"] == "DeepSORT":
valid = valid and _validate_deepsort_parameters(track_params)
elif track_params["model_type"] == "Centroid":
valid = valid and _validate_centroid_parameters(track_params)
elif track_params["model_type"] == "IOU":
valid = valid and _validate_iou_parameters(track_params)
else:
log.error("Unknown detector \"model_type\": {}".format(track_params["model_type"]))
valid = False
return valid
[docs]def _validate_sort_parameters(track_params: dict) -> bool:
valid = True
sort_params = track_params["params"]
if track_params["pref_implem"] != "Abewley":
log.error("Unknown implementation of SORT: {}".format(track_params["pref_implem"]))
valid = False
if "max_age" in sort_params and sort_params["max_age"] < 0:
log.error("\"max_age\" value must be positive.")
valid = False
if "min_hits" in sort_params and sort_params["min_hits"] < 0:
log.error("\"min_hits\" value must be positive.")
valid = False
if "iou_thresh" in sort_params and (sort_params["iou_thresh"] < 0 or sort_params["iou_thresh"] > 1):
log.error("\"iou_thresh\" value must be included between 0 and 1.")
valid = False
if "memory_fade" in sort_params and sort_params["memory_fade"] < 1:
log.error("\"memory_fade\" value must be greater than 1.")
valid = False
return valid
[docs]def _validate_deepsort_parameters(track_params: dict) -> bool:
valid = True
deepsort_params = track_params["params"]
if track_params["pref_implem"] != "Leonlok":
log.error("Unknown implementation of DeepSORT: {}".format(track_params["pref_implem"]))
valid = False
if "model_path" not in deepsort_params:
log.error("\"model_path\" sub-entry is required in \"DeepSORT\" entry.")
valid = False
elif not isinstance(deepsort_params["model_path"], str):
log.error("\"model_path\" (DeepSORT) must be of type string.")
valid = False
if "max_age" in deepsort_params and deepsort_params["max_age"] < 0:
log.error("\"max_age\" value must be positive.")
valid = False
if "min_hits" in deepsort_params and deepsort_params["min_hits"] < 0:
log.error("\"min_hits\" value must be positive.")
valid = False
if "iou_thresh" in deepsort_params and (deepsort_params["iou_thresh"] < 0 or deepsort_params["iou_thresh"] > 1):
log.error("\"iou_thresh\" value must be included between 0 and 1.")
valid = False
if "max_cosine_dist" in deepsort_params \
and (deepsort_params["max_cosine_dist"] < 0 or deepsort_params["max_cosine_dist"] > 1):
log.error("\"max_cosine_dist\" value must be included between 0 and 1.")
valid = False
if "avg_det_conf" in deepsort_params:
if not isinstance(deepsort_params.get("avg_det_conf"), bool):
log.error("\"avg_det_conf\" must be of type bool.")
valid = False
if "avg_det_conf_thresh" not in deepsort_params:
print("[ERROR \"avg_det_conf\" entry withtout \"avg_det_conf_thresh\" entry.")
valid = False
elif deepsort_params["avg_det_conf_thresh"] < 0 or deepsort_params["avg_det_conf_thresh"] > 1:
log.error("\"avg_det_conf_thresh\" value must be included between 0 and 1.")
valid = False
if "most_common_class" in deepsort_params and not isinstance(deepsort_params.get("most_common_class"), bool):
log.error("\"most_common_class\" must be of type bool.")
valid = False
return valid
[docs]def _validate_centroid_parameters(track_params: dict) -> bool:
valid = True
centroid_params = track_params["params"]
if track_params["pref_implem"] != "Rosebrock":
log.error("Unknown implementation of Centroid: {}".format(track_params["pref_implem"]))
valid = False
if "max_age" in centroid_params and centroid_params["max_age"] < 0:
log.error("\"max_age\" value must be positive.")
valid = False
return valid
[docs]def _validate_iou_parameters(track_params: dict) -> bool:
valid = True
iou_params = track_params["params"]
if track_params["pref_implem"] not in ["SimpleIOU", "KIOU"]:
log.error("Unknown implementation of Centroid: {}".format(track_params["pref_implem"]))
valid = False
if "min_hits" in iou_params and iou_params["min_hits"] < 0:
log.error("\"min_hits\" value must be positive.")
valid = False
if "iou_thresh" in iou_params and (iou_params["iou_thresh"] < 0 or iou_params["iou_thresh"] > 1):
log.error("\"iou_thresh\" value must be included between 0 and 1.")
valid = False
return valid