Repository: roboflow/supervision
File: supervision/detection/utils.py (Python)

Code before:

def merge_metadata(metadata_list: List[Dict[str, Any]]) -> Dict[str, Any]:
    """
    Merge metadata from a list of metadata dictionaries.

    This function combines the metadata dictionaries. If a key appears in more than one
    dictionary, the values must be identical for the merge to succeed.

    Args:
        metadata_list (List[Dict[str, Any]]): A list of metadata dictionaries to merge.

    Returns:
        Dict[str, Any]: A single merged metadata dictionary.

    Raises:
        ValueError: If there are conflicting values for the same key.
    """
    merged_metadata = {}

    for metadata in metadata_list:
        for key, value in metadata.items():
            if key in merged_metadata:
                if merged_metadata[key] != value:
                    raise ValueError(f"Conflicting metadata for key: {key}.")
            else:
                merged_metadata[key] = value

    return merged_metadata


def get_data_item(
    data: Dict[str, Union[np.ndarray, List]],
    index: Union[int, slice, List[int], np.ndarray],
) -> Dict[str, Union[np.ndarray, List]]:
    """
    Retrieve a subset of the data dictionary based on the given index.

    Args:
        data: The data dictionary of the Detections object.
        index: The index or indices specifying the subset to retrieve.

    Returns:
        A subset of the data dictionary corresponding to the specified index.
    """
    subset_data = {}
    for key, value in data.items():
        if isinstance(value, np.ndarray):
            subset_data[key] = value[index]
        elif isinstance(value, list):
            if isinstance(index, slice):
                subset_data[key] = value[index]

Review comment (suggestion):
Contrary to how leniently we treat `Detections.empty`, let's be more aggressive here.

Using similar logic to `__eq__`, let's make sure that for all non-empty metadata, all the keys and values are the same.

If it's different, let's raise an error.

Code after:
    merged_data = {key: [] for key in all_keys_sets[0]}
    for data in data_list:
        for key in data:
            merged_data[key].append(data[key])

    for key in merged_data:
        if all(isinstance(item, list) for item in merged_data[key]):
            merged_data[key] = list(chain.from_iterable(merged_data[key]))
        elif all(isinstance(item, np.ndarray) for item in merged_data[key]):
            ndim = merged_data[key][0].ndim
            if ndim == 1:
                merged_data[key] = np.hstack(merged_data[key])
            elif ndim > 1:
                merged_data[key] = np.vstack(merged_data[key])
            else:
                raise ValueError(f"Unexpected array dimension for key '{key}'.")
        else:
            raise ValueError(
                f"Inconsistent data types for key '{key}'. Only np.ndarray and list "
                f"types are allowed."
            )

    return merged_data


def merge_metadata(metadata_list: List[Dict[str, Any]]) -> Dict[str, Any]:
    """
    Merge metadata from a list of metadata dictionaries.

    This function combines the metadata dictionaries. If a key appears in more than one
    dictionary, the values must be identical for the merge to succeed.

    Warning: Assumes that empty detections were filtered-out before passing metadata to
    this function.

    Args:
        metadata_list (List[Dict[str, Any]]): A list of metadata dictionaries to merge.

    Returns:
        Dict[str, Any]: A single merged metadata dictionary.

    Raises:
        ValueError: If there are conflicting values for the same key or if
        dictionaries have different keys.
    """
    if not metadata_list:
        return {}

    all_keys_sets = [set(metadata.keys()) for metadata in metadata_list]
    if not all(keys_set == all_keys_sets[0] for keys_set in all_keys_sets):
        raise ValueError("All metadata dictionaries must have the same keys to merge.")