def matches_spec(o, spec, ignore_batch_dim=False):
    """
    Test whether data object matches the desired spec.

    @param o: Data object.
    @param spec: Metadata for describing the the data object.
    @param ignore_batch_dim: Ignore first dimension when checking the shapes.

    @return: True if the data object matches the spec, otherwise False.
    """
    if isinstance(spec, (list, tuple)):
        if not isinstance(o, (list, tuple)):
            raise ValueError('data object is not a list or tuple which is required by the spec: {}'.format(spec))
        if len(spec) != len(o):
            raise ValueError('data object has a different number of elements than the spec: {}'.format(spec))
        for i in range(len(spec)):
            if not matches_spec(o[i], spec[i], ignore_batch_dim=ignore_batch_dim):
                return False
        return True
    elif isinstance(spec, dict):
        if not isinstance(o, dict):
            raise ValueError('data object is not a dict which is required by the spec: {}'.format(spec))
        if spec.keys() != o.keys():
            raise ValueError('data object has different keys than those specified in the spec: {}'.format(spec))
        for k in spec:
            if not matches_spec(o[k], spec[k], ignore_batch_dim=ignore_batch_dim):
                return False
            return True
    else:
        spec_shape = spec.shape[1:] if ignore_batch_dim else spec.shape
        o_shape = o.shape[1:] if ignore_batch_dim else o.shape
        return spec_shape == o_shape and spec.dtype == o.dtype